Unverified Commit 49dd522b authored by justcoding121's avatar justcoding121 Committed by GitHub

Merge pull request #420 from justcoding121/beta

Stable
parents 83377610 347fa30b
param (
[string]$Action="default",
[hashtable]$properties=@{},
[switch]$Help
)
$Here = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
Import-Module "$Here\Common"
Install-Chocolatey
Install-Psake
$psakeDirectory = (Resolve-Path $env:ChocolateyInstall\lib\Psake*)
Import-Module (Join-Path $psakeDirectory "tools\Psake\Psake.psm1")
if($Help)
{
try
{
Write-Host "Available build tasks:"
psake -nologo -docs | Out-Host -paging
}
catch {}
return
}
Invoke-Psake -buildFile "$Here\Default.ps1" -parameters $properties -tasklist $Action
\ No newline at end of file
...@@ -5,59 +5,119 @@ $Here = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)" ...@@ -5,59 +5,119 @@ $Here = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
$SolutionRoot = (Split-Path -parent $Here) $SolutionRoot = (Split-Path -parent $Here)
$ProjectName = "Titanium.Web.Proxy" $ProjectName = "Titanium.Web.Proxy"
$GitHubProjectName = "Titanium-Web-Proxy"
$GitHubUserName = "justcoding121"
$SolutionFile = "$SolutionRoot\$ProjectName.sln" $SolutionFile = "$SolutionRoot\$ProjectName.sln"
## This comes from the build server iteration ## This comes from the build server iteration
if(!$BuildNumber) { $BuildNumber = $env:APPVEYOR_BUILD_NUMBER } if(!$BuildNumber) { $BuildNumber = $env:APPVEYOR_BUILD_NUMBER }
if(!$BuildNumber) { $BuildNumber = "1"} if(!$BuildNumber) { $BuildNumber = "0"}
## The build configuration, i.e. Debug/Release ## The build configuration, i.e. Debug/Release
if(!$Configuration) { $Configuration = $env:Configuration } if(!$Configuration) { $Configuration = $env:Configuration }
if(!$Configuration) { $Configuration = "Release" } if(!$Configuration) { $Configuration = "Release" }
if(!$Version) { $Version = $env:APPVEYOR_BUILD_VERSION } if(!$Version) { $Version = $env:APPVEYOR_BUILD_VERSION }
if(!$Version) { $Version = "1.0.$BuildNumber" } if(!$Version) { $Version = "0.0.$BuildNumber" }
if(!$Branch) { $Branch = $env:APPVEYOR_REPO_BRANCH } if(!$Branch) { $Branch = $env:APPVEYOR_REPO_BRANCH }
if(!$Branch) { $Branch = "local" } if(!$Branch) { $Branch = "local" }
if($Branch -eq "beta" ) { $Version = "$Version-beta" } if($Branch -eq "beta" ) { $Version = "$Version-beta" }
Import-Module "$Here\Common" -DisableNameChecking
$NuGet = Join-Path $SolutionRoot ".nuget\nuget.exe" $NuGet = Join-Path $SolutionRoot ".nuget\nuget.exe"
$MSBuild = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\msbuild.exe" $MSBuild = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\msbuild.exe"
$MSBuild -replace ' ', '` ' $MSBuild -replace ' ', '` '
FormatTaskName (("-"*25) + "[{0}]" + ("-"*25)) FormatTaskName (("-"*25) + "[{0}]" + ("-"*25))
Task default -depends Clean, Build, Package #default task
Task default -depends Clean, Build, Document, Package
Task Build -depends Restore-Packages{
exec { . $MSBuild $SolutionFile /t:Build /v:normal /p:Configuration=$Configuration /t:restore }
}
Task Package -depends Build {
exec { . $NuGet pack "$SolutionRoot\Titanium.Web.Proxy\Titanium.Web.Proxy.nuspec" -Properties Configuration=$Configuration -OutputDirectory "$SolutionRoot" -Version "$Version" }
}
Task Clean -depends Install-BuildTools { #cleans obj, b
Task Clean {
Get-ChildItem .\ -include bin,obj -Recurse | foreach ($_) { Remove-Item $_.fullname -Force -Recurse } Get-ChildItem .\ -include bin,obj -Recurse | foreach ($_) { Remove-Item $_.fullname -Force -Recurse }
exec { . $MSBuild $SolutionFile /t:Clean /v:quiet } exec { . $MSBuild $SolutionFile /t:Clean /v:quiet }
} }
Task Restore-Packages { #install build tools
exec { . dotnet restore "$SolutionRoot\Titanium.Web.Proxy.sln" } Task Install-BuildTools -depends Clean {
}
Task Install-MSBuild {
if(!(Test-Path $MSBuild)) if(!(Test-Path $MSBuild))
{ {
cinst microsoft-build-tools -y cinst microsoft-build-tools -y
} }
} }
Task Install-BuildTools -depends Install-MSBuild #restore nuget packages
\ No newline at end of file Task Restore-Packages -depends Install-BuildTools {
exec { . dotnet restore "$SolutionRoot\$ProjectName.sln" }
}
#build
Task Build -depends Restore-Packages{
exec { . $MSBuild $SolutionFile /t:Build /v:normal /p:Configuration=$Configuration /t:restore }
}
#publish API documentation changes for GitHub pages under master\docs directory
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"
$unsorted = Get-Content $IndexJsonFile | Out-String
[Reflection.Assembly]::LoadFile("$Here\lib\Newtonsoft.Json.dll")
[System.Reflection.Assembly]::LoadWithPartialName("System")
$hashTable = [Newtonsoft.Json.JsonConvert]::DeserializeObject($unsorted, [System.Collections.Generic.SortedDictionary[[string],[object]]])
$obj = [Newtonsoft.Json.JsonConvert]::SerializeObject($hashTable, [Newtonsoft.Json.Formatting]::Indented)
Set-Content -Path $IndexJsonFile -Value $obj
#setup clone directory
$TEMP_REPO_DIR =(Split-Path -parent $SolutionRoot) + "\temp-repo-clone"
If(test-path $TEMP_REPO_DIR)
{
Remove-Item $TEMP_REPO_DIR -Force -Recurse
}
New-Item -ItemType Directory -Force -Path $TEMP_REPO_DIR
#clone
git clone https://github.com/$GitHubUserName/$GitHubProjectName.git --branch master $TEMP_REPO_DIR
If(test-path "$TEMP_REPO_DIR\docs")
{
Remove-Item "$TEMP_REPO_DIR\docs" -Force -Recurse
}
New-Item -ItemType Directory -Force -Path "$TEMP_REPO_DIR\docs"
#cd to docs folder
cd "$TEMP_REPO_DIR\docs"
#copy docs to clone directory\docs
Copy-Item -Path "$SolutionRoot\docs\*" -Destination "$TEMP_REPO_DIR\docs" -Recurse -Force
#push changes to master
git config --global credential.helper store
Add-Content "$HOME\.git-credentials" "https://$($env:github_access_token):x-oauth-basic@github.com`n"
git config --global user.email $env:github_email
git config --global user.name "buildbot121"
git add . -A
git commit -m "API documentation update by build server"
git push origin master
#move cd back to current location
cd $Here
}
}
#package nuget files
Task Package -depends Document {
exec { . $NuGet pack "$SolutionRoot\$ProjectName\$ProjectName.nuspec" -Properties Configuration=$Configuration -OutputDirectory "$SolutionRoot" -Version "$Version" }
}
{
"metadata": [
{
"src": [
{
"files": [ "Titanium.Web.Proxy.Docs.sln"],
"src": "../"
}
],
"dest": "obj/api"
}
],
"build": {
"content": [
{
"files": [ "**/*.yml" ],
"src": "obj/api",
"dest": "api"
},
{
"files": [ "*.md" ]
}
],
"resource": [
{
"files": [ ""]
}
],
"overwrite": "specs/*.md",
"globalMetadata": {
"_appTitle": "Titanium Web Proxy",
"_enableSearch": true
},
"dest": "../docs",
"xrefService": [ "https://xref.docs.microsoft.com/query?uid={uid}" ]
}
}
### param (
### Common Profile functions for all users [string]$Action="default",
### [hashtable]$properties=@{},
[switch]$Help
$ErrorActionPreference = 'Stop' )
Set-StrictMode -Version Latest
$ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
$SolutionRoot = Split-Path -Parent $ScriptPath
$ToolsPath = Join-Path -Path $SolutionRoot -ChildPath "lib"
Export-ModuleMember -Variable @('ScriptPath', 'SolutionRoot', 'ToolsPath')
function Install-Chocolatey() function Install-Chocolatey()
{ {
...@@ -20,6 +11,7 @@ function Install-Chocolatey() ...@@ -20,6 +11,7 @@ function Install-Chocolatey()
Write-Output "Chocolatey Not Found, Installing..." Write-Output "Chocolatey Not Found, Installing..."
iex ((new-object net.webclient).DownloadString('http://chocolatey.org/install.ps1')) iex ((new-object net.webclient).DownloadString('http://chocolatey.org/install.ps1'))
} }
$env:Path += ";${env:ChocolateyInstall}"
} }
function Install-Psake() function Install-Psake()
...@@ -30,4 +22,60 @@ function Install-Psake() ...@@ -30,4 +22,60 @@ function Install-Psake()
} }
} }
Export-ModuleMember -Function *-* function Install-Git()
\ No newline at end of file {
if(!((Test-Path ${env:ProgramFiles(x86)}\Git*) -Or (Test-Path ${env:ProgramFiles}\Git*)))
{
choco install git.install
}
$env:Path += ";${env:ProgramFiles(x86)}\Git"
$env:Path += ";${env:ProgramFiles}\Git"
}
function Install-DocFx()
{
if(!(Test-Path $env:ChocolateyInstall\lib\docfx\tools*))
{
choco install docfx
}
$env:Path += ";$env:ChocolateyInstall\lib\docfx\tools"
}
#current directory
$Here = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
$ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
$SolutionRoot = Split-Path -Parent $ScriptPath
$ToolsPath = Join-Path -Path $SolutionRoot -ChildPath "lib"
if(-not $env:ChocolateyInstall)
{
$env:ChocolateyInstall = "${env:ALLUSERSPROFILE}\chocolatey";
}
Install-Chocolatey
Install-Psake
Install-Git
Install-DocFx
$psakeDirectory = (Resolve-Path $env:ChocolateyInstall\lib\Psake*)
#appveyor for some reason have different location for psake (it has older psake version?)
if(Test-Path $psakeDirectory\tools\Psake\Psake.psm*)
{
Import-Module (Join-Path $psakeDirectory "tools\Psake\Psake.psm1")
}
else
{
Import-Module (Join-Path $psakeDirectory "tools\Psake.psm1")
}
#invoke the task
Invoke-Psake -buildFile "$Here\build.ps1" -parameters $properties -tasklist $Action
...@@ -203,3 +203,6 @@ FakesAssemblies/ ...@@ -203,3 +203,6 @@ FakesAssemblies/
# Visual Studio 6 workspace options file # Visual Studio 6 workspace options file
*.opt *.opt
# Docfx
docs/manifest.json
\ No newline at end of file
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup> </startup>
</configuration> </configuration>
\ No newline at end of file
...@@ -4,32 +4,31 @@ using System.Runtime.InteropServices; ...@@ -4,32 +4,31 @@ using System.Runtime.InteropServices;
namespace Titanium.Web.Proxy.Examples.Basic.Helpers namespace Titanium.Web.Proxy.Examples.Basic.Helpers
{ {
/// <summary> /// <summary>
/// Adapated from /// Adapated from
/// http://stackoverflow.com/questions/13656846/how-to-programmatic-disable-c-sharp-console-applications-quick-edit-mode /// http://stackoverflow.com/questions/13656846/how-to-programmatic-disable-c-sharp-console-applications-quick-edit-mode
/// </summary> /// </summary>
internal static class ConsoleHelper internal static class ConsoleHelper
{ {
const uint ENABLE_QUICK_EDIT = 0x0040; private const uint ENABLE_QUICK_EDIT = 0x0040;
// STD_INPUT_HANDLE (DWORD): -10 is the standard input device. // STD_INPUT_HANDLE (DWORD): -10 is the standard input device.
const int STD_INPUT_HANDLE = -10; private const int STD_INPUT_HANDLE = -10;
[DllImport("kernel32.dll", SetLastError = true)] [DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr GetStdHandle(int nStdHandle); private static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll")] [DllImport("kernel32.dll")]
static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode); private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
[DllImport("kernel32.dll")] [DllImport("kernel32.dll")]
static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode); private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
internal static bool DisableQuickEditMode() internal static bool DisableQuickEditMode()
{ {
var consoleHandle = GetStdHandle(STD_INPUT_HANDLE); var consoleHandle = GetStdHandle(STD_INPUT_HANDLE);
// get current console mode // get current console mode
uint consoleMode; if (!GetConsoleMode(consoleHandle, out uint consoleMode))
if (!GetConsoleMode(consoleHandle, out consoleMode))
{ {
// ERROR: Unable to get console mode. // ERROR: Unable to get console mode.
return false; return false;
......
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup> </startup>
</configuration> </configuration>
\ No newline at end of file
...@@ -4,6 +4,6 @@ ...@@ -4,6 +4,6 @@
xmlns:local="clr-namespace:Titanium.Web.Proxy.Examples.Wpf" xmlns:local="clr-namespace:Titanium.Web.Proxy.Examples.Wpf"
StartupUri="MainWindow.xaml"> StartupUri="MainWindow.xaml">
<Application.Resources> <Application.Resources>
</Application.Resources> </Application.Resources>
</Application> </Application>
\ No newline at end of file
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
namespace Titanium.Web.Proxy.Examples.Wpf namespace Titanium.Web.Proxy.Examples.Wpf
{ {
/// <summary> /// <summary>
/// Interaction logic for App.xaml /// Interaction logic for App.xaml
/// </summary> /// </summary>
public partial class App : Application public partial class App : Application
{ {
......
...@@ -6,50 +6,51 @@ ...@@ -6,50 +6,51 @@
xmlns:local="clr-namespace:Titanium.Web.Proxy.Examples.Wpf" xmlns:local="clr-namespace:Titanium.Web.Proxy.Examples.Wpf"
mc:Ignorable="d" mc:Ignorable="d"
Title="MainWindow" Height="500" Width="1000" WindowState="Maximized" Title="MainWindow" Height="500" Width="1000" WindowState="Maximized"
DataContext="{Binding RelativeSource={RelativeSource Self}}"> DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="500" /> <ColumnDefinition Width="500" />
<ColumnDefinition Width="3" /> <ColumnDefinition Width="3" />
<ColumnDefinition /> <ColumnDefinition />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition /> <RowDefinition />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<GridSplitter Grid.Column="1" Grid.Row="0" HorizontalAlignment="Stretch" /> <GridSplitter Grid.Column="1" Grid.Row="0" HorizontalAlignment="Stretch" />
<ListView Grid.Column="0" Grid.Row="0" HorizontalAlignment="Stretch" ItemsSource="{Binding Sessions}" SelectedItem="{Binding SelectedSession}" <ListView Grid.Column="0" Grid.Row="0" HorizontalAlignment="Stretch" ItemsSource="{Binding Sessions}"
KeyDown="ListViewSessions_OnKeyDown"> SelectedItem="{Binding SelectedSession}"
<ListView.View> KeyDown="ListViewSessions_OnKeyDown">
<GridView> <ListView.View>
<GridViewColumn Header="Result" DisplayMemberBinding="{Binding StatusCode}" /> <GridView>
<GridViewColumn Header="Protocol" DisplayMemberBinding="{Binding Protocol}" /> <GridViewColumn Header="Result" DisplayMemberBinding="{Binding StatusCode}" />
<GridViewColumn Header="Host" DisplayMemberBinding="{Binding Host}" /> <GridViewColumn Header="Protocol" DisplayMemberBinding="{Binding Protocol}" />
<GridViewColumn Header="Url" DisplayMemberBinding="{Binding Url}" /> <GridViewColumn Header="Host" DisplayMemberBinding="{Binding Host}" />
<GridViewColumn Header="BodySize" DisplayMemberBinding="{Binding BodySize}" /> <GridViewColumn Header="Url" DisplayMemberBinding="{Binding Url}" />
<GridViewColumn Header="Process" DisplayMemberBinding="{Binding Process}" /> <GridViewColumn Header="BodySize" DisplayMemberBinding="{Binding BodySize}" />
<GridViewColumn Header="SentBytes" DisplayMemberBinding="{Binding SentDataCount}" /> <GridViewColumn Header="Process" DisplayMemberBinding="{Binding Process}" />
<GridViewColumn Header="ReceivedBytes" DisplayMemberBinding="{Binding ReceivedDataCount}" /> <GridViewColumn Header="SentBytes" DisplayMemberBinding="{Binding SentDataCount}" />
</GridView> <GridViewColumn Header="ReceivedBytes" DisplayMemberBinding="{Binding ReceivedDataCount}" />
</ListView.View> </GridView>
</ListView> </ListView.View>
<TabControl Grid.Column="2" Grid.Row="0"> </ListView>
<TabItem Header="Session"> <TabControl Grid.Column="2" Grid.Row="0">
<Grid Background="Red" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> <TabItem Header="Session">
<Grid.RowDefinitions> <Grid Background="Red" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<RowDefinition /> <Grid.RowDefinitions>
<RowDefinition /> <RowDefinition />
</Grid.RowDefinitions> <RowDefinition />
<TextBox x:Name="TextBoxRequest" Grid.Row="0" /> </Grid.RowDefinitions>
<TextBox x:Name="TextBoxResponse" Grid.Row="1" /> <TextBox x:Name="TextBoxRequest" Grid.Row="0" />
</Grid> <TextBox x:Name="TextBoxResponse" Grid.Row="1" />
</TabItem> </Grid>
</TabControl> </TabItem>
<StackPanel Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="3" Orientation="Horizontal"> </TabControl>
<TextBlock Text="ClientConnectionCount:" /> <StackPanel Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="3" Orientation="Horizontal">
<TextBlock Text="{Binding ClientConnectionCount}" Margin="10,0,20,0" /> <TextBlock Text="ClientConnectionCount:" />
<TextBlock Text="ServerConnectionCount:" /> <TextBlock Text="{Binding ClientConnectionCount}" Margin="10,0,20,0" />
<TextBlock Text="{Binding ServerConnectionCount}" Margin="10,0,20,0" /> <TextBlock Text="ServerConnectionCount:" />
</StackPanel> <TextBlock Text="{Binding ServerConnectionCount}" Margin="10,0,20,0" />
</StackPanel>
</Grid> </Grid>
</Window> </Window>
\ No newline at end of file
using System.Reflection; using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Windows; using System.Windows;
...@@ -33,11 +31,11 @@ using System.Windows; ...@@ -33,11 +31,11 @@ using System.Windows;
[assembly: ThemeInfo( [assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page, //(used if a resource is not found in the page,
// or application resource dictionaries) // or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page, //(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries) // app, or any theme specific resource dictionaries)
)] )]
......
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)"> <SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles> <Profiles>
<Profile Name="(Default)" /> <Profile Name="(Default)" />
......
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Examples.Wpf.Annotations; using Titanium.Web.Proxy.Examples.Wpf.Annotations;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
...@@ -9,14 +8,15 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -9,14 +8,15 @@ namespace Titanium.Web.Proxy.Examples.Wpf
{ {
public class SessionListItem : INotifyPropertyChanged public class SessionListItem : INotifyPropertyChanged
{ {
private string statusCode; private long? bodySize;
private string protocol; private Exception exception;
private string host; private string host;
private string url;
private long bodySize;
private string process; private string process;
private string protocol;
private long receivedDataCount; private long receivedDataCount;
private long sentDataCount; private long sentDataCount;
private string statusCode;
private string url;
public int Number { get; set; } public int Number { get; set; }
...@@ -26,58 +26,67 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -26,58 +26,67 @@ namespace Titanium.Web.Proxy.Examples.Wpf
public string StatusCode public string StatusCode
{ {
get { return statusCode; } get => statusCode;
set { SetField(ref statusCode, value);} set => SetField(ref statusCode, value);
} }
public string Protocol public string Protocol
{ {
get { return protocol; } get => protocol;
set { SetField(ref protocol, value); } set => SetField(ref protocol, value);
} }
public string Host public string Host
{ {
get { return host; } get => host;
set { SetField(ref host, value); } set => SetField(ref host, value);
} }
public string Url public string Url
{ {
get { return url; } get => url;
set { SetField(ref url, value); } set => SetField(ref url, value);
} }
public long BodySize public long? BodySize
{ {
get { return bodySize; } get => bodySize;
set { SetField(ref bodySize, value); } set => SetField(ref bodySize, value);
} }
public string Process public string Process
{ {
get { return process; } get => process;
set { SetField(ref process, value); } set => SetField(ref process, value);
} }
public long ReceivedDataCount public long ReceivedDataCount
{ {
get { return receivedDataCount; } get => receivedDataCount;
set { SetField(ref receivedDataCount, value); } set => SetField(ref receivedDataCount, value);
} }
public long SentDataCount public long SentDataCount
{ {
get { return sentDataCount; } get => sentDataCount;
set { SetField(ref sentDataCount, value); } set => SetField(ref sentDataCount, value);
}
public Exception Exception
{
get => exception;
set => SetField(ref exception, value);
} }
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
protected void SetField<T>(ref T field, T value,[CallerMemberName] string propertyName = null) protected void SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{ {
field = value; if (!Equals(field, value))
OnPropertyChanged(propertyName); {
field = value;
OnPropertyChanged(propertyName);
}
} }
[NotifyPropertyChangedInvocator] [NotifyPropertyChangedInvocator]
...@@ -105,7 +114,24 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -105,7 +114,24 @@ namespace Titanium.Web.Proxy.Examples.Wpf
Url = request.RequestUri.AbsolutePath; Url = request.RequestUri.AbsolutePath;
} }
BodySize = response?.ContentLength ?? -1; if (!IsTunnelConnect)
{
long responseSize = -1;
if (response != null)
{
if (response.ContentLength != -1)
{
responseSize = response.ContentLength;
}
else if (response.IsBodyRead && response.Body != null)
{
responseSize = response.Body.Length;
}
}
BodySize = responseSize;
}
Process = GetProcessDescription(WebSession.ProcessId.Value); Process = GetProcessDescription(WebSession.ProcessId.Value);
} }
......
...@@ -51,8 +51,8 @@ ...@@ -51,8 +51,8 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="StreamExtended, Version=1.0.81.0, Culture=neutral, PublicKeyToken=bbfa0f1d54f50043, processorArchitecture=MSIL"> <Reference Include="StreamExtended, Version=1.0.147.0, Culture=neutral, PublicKeyToken=bbfa0f1d54f50043, processorArchitecture=MSIL">
<HintPath>..\..\packages\StreamExtended.1.0.81\lib\net45\StreamExtended.dll</HintPath> <HintPath>..\..\packages\StreamExtended.1.0.147-beta\lib\net45\StreamExtended.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="StreamExtended" version="1.0.81" targetFramework="net45" /> <package id="StreamExtended" version="1.0.147-beta" targetFramework="net45" />
</packages> </packages>
\ No newline at end of file
...@@ -2,19 +2,20 @@ ...@@ -2,19 +2,20 @@
A light weight HTTP(S) proxy server written in C# A light weight HTTP(S) proxy server written in C#
<a href="https://ci.appveyor.com/project/justcoding121/titanium-web-proxy">![Build Status](https://ci.appveyor.com/api/projects/status/rvlxv8xgj0m7lkr4?svg=true)</a> <a href="https://ci.appveyor.com/project/justcoding121/titanium-web-proxy">![Build Status](https://ci.appveyor.com/api/projects/status/rvlxv8xgj0m7lkr4?svg=true)</a> [![Join the chat at https://gitter.im/Titanium-Web-Proxy/Lobby](https://badges.gitter.im/Titanium-Web-Proxy/Lobby.svg)](https://gitter.im/Titanium-Web-Proxy/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Kindly report only issues/bugs here . For programming help or questions use [StackOverflow](http://stackoverflow.com/questions/tagged/titanium-web-proxy) with the tag Titanium-Web-Proxy. Kindly report only issues/bugs here . For programming help or questions use [StackOverflow](http://stackoverflow.com/questions/tagged/titanium-web-proxy) with the tag Titanium-Web-Proxy.
([Wiki & Contribution guidelines](https://github.com/justcoding121/Titanium-Web-Proxy/wiki)) * [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** **Console example application screenshot**
![alt tag](https://raw.githubusercontent.com/justcoding121/Titanium-Web-Proxy/develop/Examples/Titanium.Web.Proxy.Examples.Basic/Capture.PNG) ![alt tag](https://raw.githubusercontent.com/justcoding121/Titanium-Web-Proxy/master/Examples/Titanium.Web.Proxy.Examples.Basic/Capture.PNG)
**GUI example application screenshot** **GUI example application screenshot**
![alt tag](https://raw.githubusercontent.com/justcoding121/Titanium-Web-Proxy/develop/Examples/Titanium.Web.Proxy.Examples.Wpf/Capture.PNG) ![alt tag](https://raw.githubusercontent.com/justcoding121/Titanium-Web-Proxy/master/Examples/Titanium.Web.Proxy.Examples.Wpf/Capture.PNG)
### Features ### Features
...@@ -52,11 +53,11 @@ Setup HTTP proxy: ...@@ -52,11 +53,11 @@ Setup HTTP proxy:
var proxyServer = new ProxyServer(); var proxyServer = new ProxyServer();
//locally trust root certificate used by this proxy //locally trust root certificate used by this proxy
proxyServer.TrustRootCertificate = true; proxyServer.CertificateManager.TrustRootCertificate = true;
//optionally set the Certificate Engine //optionally set the Certificate Engine
//Under Mono only BouncyCastle will be supported //Under Mono only BouncyCastle will be supported
//proxyServer.CertificateEngine = Network.CertificateEngine.BouncyCastle; //proxyServer.CertificateManager.CertificateEngine = Network.CertificateEngine.BouncyCastle;
proxyServer.BeforeRequest += OnRequest; proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse; proxyServer.BeforeResponse += OnResponse;
...@@ -66,17 +67,15 @@ proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection; ...@@ -66,17 +67,15 @@ proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true) var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{ {
//Exclude HTTPS addresses you don't want to proxy //Use self-issued generic certificate on all https requests
//Useful for clients that use certificate pinning //Optimizes performance by not creating a certificate for each https-enabled domain
//for example dropbox.com
// ExcludedHttpsHostNameRegex = new List<string>() { "google.com", "dropbox.com" }
//Use self-issued generic certificate on all HTTPS requests
//Optimizes performance by not creating a certificate for each HTTPS-enabled domain
//Useful when certificate trust is not required by proxy clients //Useful when certificate trust is not required by proxy clients
// GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password") //GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password")
}; };
//Fired when a CONNECT request is received
explicitEndPoint.BeforeTunnelConnect += OnBeforeTunnelConnect;
//An explicit endpoint is where the client knows about the existence of a proxy //An explicit endpoint is where the client knows about the existence of a proxy
//So client sends request in a proxy friendly manner //So client sends request in a proxy friendly manner
proxyServer.AddEndPoint(explicitEndPoint); proxyServer.AddEndPoint(explicitEndPoint);
...@@ -109,6 +108,7 @@ proxyServer.SetAsSystemHttpsProxy(explicitEndPoint); ...@@ -109,6 +108,7 @@ proxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
Console.Read(); Console.Read();
//Unsubscribe & Quit //Unsubscribe & Quit
explicitEndPoint.BeforeTunnelConnect -= OnBeforeTunnelConnect;
proxyServer.BeforeRequest -= OnRequest; proxyServer.BeforeRequest -= OnRequest;
proxyServer.BeforeResponse -= OnResponse; proxyServer.BeforeResponse -= OnResponse;
proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation; proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation;
...@@ -125,6 +125,19 @@ Sample request and response event handlers ...@@ -125,6 +125,19 @@ Sample request and response event handlers
private IDictionary<Guid, string> requestBodyHistory private IDictionary<Guid, string> requestBodyHistory
= new ConcurrentDictionary<Guid, string>(); = new ConcurrentDictionary<Guid, string>();
private async Task OnBeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
{
string hostname = e.WebSession.Request.RequestUri.Host;
if (hostname.Contains("dropbox.com"))
{
//Exclude Https addresses you don't want to proxy
//Useful for clients that use certificate pinning
//for example dropbox.com
e.DecryptSsl = false;
}
}
public async Task OnRequest(object sender, SessionEventArgs e) public async Task OnRequest(object sender, SessionEventArgs e)
{ {
Console.WriteLine(e.WebSession.Request.Url); Console.WriteLine(e.WebSession.Request.Url);
...@@ -152,7 +165,7 @@ public async Task OnRequest(object sender, SessionEventArgs e) ...@@ -152,7 +165,7 @@ public async Task OnRequest(object sender, SessionEventArgs e)
//Filter URL //Filter URL
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com")) if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
{ {
await e.Ok("<!DOCTYPE html>" + e.Ok("<!DOCTYPE html>" +
"<html><body><h1>" + "<html><body><h1>" +
"Website Blocked" + "Website Blocked" +
"</h1>" + "</h1>" +
...@@ -163,7 +176,7 @@ public async Task OnRequest(object sender, SessionEventArgs e) ...@@ -163,7 +176,7 @@ public async Task OnRequest(object sender, SessionEventArgs e)
//Redirect example //Redirect example
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org")) if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
{ {
await e.Redirect("https://www.paypal.com"); e.Redirect("https://www.paypal.com");
} }
} }
...@@ -218,7 +231,6 @@ public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs ...@@ -218,7 +231,6 @@ public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs
#### Roadmap #### Roadmap
* Support HTTP 2.0 * Support HTTP 2.0
* Support SOCKS protocol
#### Collaborators #### Collaborators
......
...@@ -35,7 +35,7 @@ namespace Titanium.Web.Proxy.IntegrationTests ...@@ -35,7 +35,7 @@ namespace Titanium.Web.Proxy.IntegrationTests
var handler = new HttpClientHandler var handler = new HttpClientHandler
{ {
Proxy = new WebProxy($"http://localhost:{localProxyPort}", false), Proxy = new WebProxy($"http://localhost:{localProxyPort}", false),
UseProxy = true, UseProxy = true
}; };
var client = new HttpClient(handler); var client = new HttpClient(handler);
...@@ -51,7 +51,6 @@ namespace Titanium.Web.Proxy.IntegrationTests ...@@ -51,7 +51,6 @@ namespace Titanium.Web.Proxy.IntegrationTests
public ProxyTestController() public ProxyTestController()
{ {
proxyServer = new ProxyServer(); proxyServer = new ProxyServer();
proxyServer.TrustRootCertificate = true;
} }
public void StartProxy(int proxyPort) public void StartProxy(int proxyPort)
...@@ -69,8 +68,10 @@ namespace Titanium.Web.Proxy.IntegrationTests ...@@ -69,8 +68,10 @@ namespace Titanium.Web.Proxy.IntegrationTests
proxyServer.Start(); proxyServer.Start();
foreach (var endPoint in proxyServer.ProxyEndPoints) foreach (var endPoint in proxyServer.ProxyEndPoints)
{
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ", Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port); endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
}
} }
public void Stop() public void Stop()
...@@ -97,7 +98,7 @@ namespace Titanium.Web.Proxy.IntegrationTests ...@@ -97,7 +98,7 @@ namespace Titanium.Web.Proxy.IntegrationTests
} }
/// <summary> /// <summary>
/// Allows overriding default certificate validation logic /// Allows overriding default certificate validation logic
/// </summary> /// </summary>
/// <param name="sender"></param> /// <param name="sender"></param>
/// <param name="e"></param> /// <param name="e"></param>
...@@ -113,7 +114,7 @@ namespace Titanium.Web.Proxy.IntegrationTests ...@@ -113,7 +114,7 @@ namespace Titanium.Web.Proxy.IntegrationTests
} }
/// <summary> /// <summary>
/// Allows overriding default client certificate selection logic during mutual authentication /// Allows overriding default client certificate selection logic during mutual authentication
/// </summary> /// </summary>
/// <param name="sender"></param> /// <param name="sender"></param>
/// <param name="e"></param> /// <param name="e"></param>
......
...@@ -15,25 +15,25 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -15,25 +15,25 @@ namespace Titanium.Web.Proxy.UnitTests
private readonly Random random = new Random(); private readonly Random random = new Random();
[TestMethod] [TestMethod]
public async Task Simple_Create_Certificate_Stress_Test() public async Task Simple_BC_Create_Certificate_Test()
{ {
var tasks = new List<Task>(); var tasks = new List<Task>();
var mgr = new CertificateManager(new Lazy<Action<Exception>>(() => (e => { })).Value); var mgr = new CertificateManager(null, null, false, false, false, new Lazy<ExceptionHandler>(() => (e =>
{
mgr.ClearIdleCertificates(1); //Console.WriteLine(e.ToString() + e.InnerException != null ? e.InnerException.ToString() : string.Empty);
})).Value);
for (int i = 0; i < 1000; i++) mgr.CertificateEngine = CertificateEngine.BouncyCastle;
mgr.ClearIdleCertificates();
for (int i = 0; i < 5; i++)
{ {
foreach (string host in hostNames) foreach (string host in hostNames)
{ {
tasks.Add(Task.Run(async () => tasks.Add(Task.Run(() =>
{ {
await Task.Delay(random.Next(0, 10) * 1000);
//get the connection //get the connection
var certificate = mgr.CreateCertificate(host, false); var certificate = mgr.CreateCertificate(host, false);
Assert.IsNotNull(certificate); Assert.IsNotNull(certificate);
})); }));
} }
...@@ -43,5 +43,39 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -43,5 +43,39 @@ namespace Titanium.Web.Proxy.UnitTests
mgr.StopClearIdleCertificates(); mgr.StopClearIdleCertificates();
} }
//uncomment this to compare WinCert maker performance with BC (BC takes more time for same test above)
[TestMethod]
public async Task Simple_Create_Win_Certificate_Test()
{
var tasks = new List<Task>();
var mgr = new CertificateManager(null, null, false, false, false, new Lazy<ExceptionHandler>(() => (e =>
{
//Console.WriteLine(e.ToString() + e.InnerException != null ? e.InnerException.ToString() : string.Empty);
})).Value);
mgr.CertificateEngine = CertificateEngine.DefaultWindows;
mgr.CreateRootCertificate(true);
mgr.TrustRootCertificate(true);
mgr.ClearIdleCertificates();
for (int i = 0; i < 5; i++)
{
foreach (string host in hostNames)
{
tasks.Add(Task.Run(() =>
{
//get the connection
var certificate = mgr.CreateCertificate(host, false);
Assert.IsNotNull(certificate);
}));
}
}
await Task.WhenAll(tasks.ToArray());
mgr.RemoveTrustedRootCertificate(true);
mgr.StopClearIdleCertificates();
}
} }
} }
...@@ -9,7 +9,8 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -9,7 +9,8 @@ namespace Titanium.Web.Proxy.UnitTests
public class ProxyServerTests public class ProxyServerTests
{ {
[TestMethod] [TestMethod]
public void GivenOneEndpointIsAlreadyAddedToAddress_WhenAddingNewEndpointToExistingAddress_ThenExceptionIsThrown() public void
GivenOneEndpointIsAlreadyAddedToAddress_WhenAddingNewEndpointToExistingAddress_ThenExceptionIsThrown()
{ {
// Arrange // Arrange
var proxy = new ProxyServer(); var proxy = new ProxyServer();
...@@ -34,7 +35,8 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -34,7 +35,8 @@ namespace Titanium.Web.Proxy.UnitTests
} }
[TestMethod] [TestMethod]
public void GivenOneEndpointIsAlreadyAddedToAddress_WhenAddingNewEndpointToExistingAddress_ThenTwoEndpointsExists() public void
GivenOneEndpointIsAlreadyAddedToAddress_WhenAddingNewEndpointToExistingAddress_ThenTwoEndpointsExists()
{ {
// Arrange // Arrange
var proxy = new ProxyServer(); var proxy = new ProxyServer();
...@@ -74,7 +76,8 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -74,7 +76,8 @@ namespace Titanium.Web.Proxy.UnitTests
} }
[TestMethod] [TestMethod]
public void GivenOneEndpointIsAlreadyAddedToZeroPort_WhenAddingNewEndpointToExistingPort_ThenTwoEndpointsExists() public void
GivenOneEndpointIsAlreadyAddedToZeroPort_WhenAddingNewEndpointToExistingPort_ThenTwoEndpointsExists()
{ {
// Arrange // Arrange
var proxy = new ProxyServer(); var proxy = new ProxyServer();
......
...@@ -3,6 +3,7 @@ using System.Net; ...@@ -3,6 +3,7 @@ using System.Net;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Helpers.WinHttp; using Titanium.Web.Proxy.Helpers.WinHttp;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.UnitTests namespace Titanium.Web.Proxy.UnitTests
{ {
...@@ -85,7 +86,9 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -85,7 +86,9 @@ namespace Titanium.Web.Proxy.UnitTests
{ {
hostName = Dns.GetHostName(); hostName = Dns.GetHostName();
} }
catch{} catch
{
}
if (hostName != null) if (hostName != null)
{ {
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Titanium.Web.Proxy.UnitTests</RootNamespace> <RootNamespace>Titanium.Web.Proxy.UnitTests</RootNamespace>
<AssemblyName>Titanium.Web.Proxy.UnitTests</AssemblyName> <AssemblyName>Titanium.Web.Proxy.UnitTests</AssemblyName>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion> <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath> <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest> <IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType> <TestProjectType>UnitTest</TestProjectType>
<TargetFrameworkProfile />
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
......

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
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EBF2EA46-EA00-4350-BE1D-D86AFD699DB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EBF2EA46-EA00-4350-BE1D-D86AFD699DB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EBF2EA46-EA00-4350-BE1D-D86AFD699DB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBF2EA46-EA00-4350-BE1D-D86AFD699DB3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A250E1E5-3ABA-4FED-9A0E-6C63EB0261E0}
EndGlobalSection
EndGlobal
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/CodeAnnotations/NamespacesWithAnnotations/=Titanium_002EWeb_002EProxy_002EExamples_002EWpf_002EAnnotations/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/CodeAnnotations/NamespacesWithAnnotations/=Titanium_002EWeb_002EProxy_002EExamples_002EWpf_002EAnnotations/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_EMBEDDED_ARRANGEMENT/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/LINE_FEED_AT_FILE_END/@EntryValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/LINE_FEED_AT_FILE_END/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_EMBEDDED_STATEMENT_ON_SAME_LINE/@EntryValue">NEVER</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SIMPLE_CASE_STATEMENT_STYLE/@EntryValue">LINE_BREAK</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SIMPLE_CASE_STATEMENT_STYLE/@EntryValue">LINE_BREAK</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SIMPLE_EMBEDDED_STATEMENT_STYLE/@EntryValue">LINE_BREAK</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SIMPLE_EMBEDDED_STATEMENT_STYLE/@EntryValue">LINE_BREAK</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AFTER_TYPECAST_PARENTHESES/@EntryValue">False</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AFTER_TYPECAST_PARENTHESES/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_SINGLE_LINE_ARRAY_INITIALIZER_BRACES/@EntryValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_SINGLE_LINE_ARRAY_INITIALIZER_BRACES/@EntryValue">True</s:Boolean>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">160</s:Int64> <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">120</s:Int64>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseExplicitType</s:String> <s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseExplicitType</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseVar</s:String> <s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseVar</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BC/@EntryIndexedValue">BC</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BC/@EntryIndexedValue">BC</s:String>
...@@ -21,6 +24,11 @@ ...@@ -21,6 +24,11 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=dda2ffa1_002D435c_002D4111_002D88eb_002D1a7c93c382f0/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="Property (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="PROPERTY" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=dda2ffa1_002D435c_002D4111_002D88eb_002D1a7c93c382f0/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="Property (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="PROPERTY" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
\ No newline at end of file
...@@ -9,14 +9,15 @@ namespace Titanium.Web.Proxy ...@@ -9,14 +9,15 @@ namespace Titanium.Web.Proxy
public partial class ProxyServer public partial class ProxyServer
{ {
/// <summary> /// <summary>
/// Call back to override server certificate validation /// Call back to override server certificate validation
/// </summary> /// </summary>
/// <param name="sender"></param> /// <param name="sender">The sender object.</param>
/// <param name="certificate"></param> /// <param name="certificate">The remote certificate.</param>
/// <param name="chain"></param> /// <param name="chain">The certificate chain.</param>
/// <param name="sslPolicyErrors"></param> /// <param name="sslPolicyErrors">Ssl policy errors</param>
/// <returns></returns> /// <returns>Return true if valid certificate.</returns>
internal bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) internal bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{ {
//if user callback is registered then do it //if user callback is registered then do it
if (ServerCertificateValidationCallback != null) if (ServerCertificateValidationCallback != null)
...@@ -29,7 +30,7 @@ namespace Titanium.Web.Proxy ...@@ -29,7 +30,7 @@ namespace Titanium.Web.Proxy
}; };
//why is the sender null? //why is the sender null?
ServerCertificateValidationCallback.InvokeParallel(this, args); ServerCertificateValidationCallback.InvokeAsync(this, args, exceptionFunc).Wait();
return args.IsValid; return args.IsValid;
} }
...@@ -44,22 +45,23 @@ namespace Titanium.Web.Proxy ...@@ -44,22 +45,23 @@ namespace Titanium.Web.Proxy
} }
/// <summary> /// <summary>
/// Call back to select client certificate used for mutual authentication /// Call back to select client certificate used for mutual authentication
/// </summary> /// </summary>
/// <param name="sender"></param> /// <param name="sender">The sender.</param>
/// <param name="targetHost"></param> /// <param name="targetHost">The remote hostname.</param>
/// <param name="localCertificates"></param> /// <param name="localCertificates">Selected local certificates by SslStream.</param>
/// <param name="remoteCertificate"></param> /// <param name="remoteCertificate">The remote certificate of server.</param>
/// <param name="acceptableIssuers"></param> /// <param name="acceptableIssuers">The acceptable issues for client certificate as listed by server.</param>
/// <returns></returns> /// <returns></returns>
internal X509Certificate SelectClientCertificate(object sender, string targetHost, X509CertificateCollection localCertificates, internal X509Certificate SelectClientCertificate(object sender, string targetHost,
X509CertificateCollection localCertificates,
X509Certificate remoteCertificate, string[] acceptableIssuers) X509Certificate remoteCertificate, string[] acceptableIssuers)
{ {
X509Certificate clientCertificate = null; X509Certificate clientCertificate = null;
if (acceptableIssuers != null && acceptableIssuers.Length > 0 && localCertificates != null && localCertificates.Count > 0) if (acceptableIssuers != null && acceptableIssuers.Length > 0 && localCertificates != null &&
localCertificates.Count > 0)
{ {
// Use the first certificate that is from an acceptable issuer.
foreach (var certificate in localCertificates) foreach (var certificate in localCertificates)
{ {
string issuer = certificate.Issuer; string issuer = certificate.Issuer;
...@@ -88,7 +90,7 @@ namespace Titanium.Web.Proxy ...@@ -88,7 +90,7 @@ namespace Titanium.Web.Proxy
}; };
//why is the sender null? //why is the sender null?
ClientCertificateSelectionCallback.InvokeParallel(this, args); ClientCertificateSelectionCallback.InvokeAsync(this, args, exceptionFunc).Wait();
return args.ClientCertificate; return args.ClientCertificate;
} }
......
namespace Titanium.Web.Proxy.Compression using System;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Compression
{ {
/// <summary> /// <summary>
/// A factory to generate the compression methods based on the type of compression /// A factory to generate the compression methods based on the type of compression
/// </summary> /// </summary>
internal class CompressionFactory internal static class CompressionFactory
{ {
public ICompression Create(string type) //cache
private static readonly ICompression gzip = new GZipCompression();
private static readonly ICompression deflate = new DeflateCompression();
internal static ICompression GetCompression(string type)
{ {
switch (type) switch (type)
{ {
case "gzip": case KnownHeaders.ContentEncodingGzip:
return new GZipCompression(); return gzip;
case "deflate": case KnownHeaders.ContentEncodingDeflate:
return new DeflateCompression(); return deflate;
default: default:
return null; throw new Exception($"Unsupported compression mode: {type}");
} }
} }
} }
......
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression namespace Titanium.Web.Proxy.Compression
{ {
/// <summary> /// <summary>
/// Concrete implementation of deflate compression /// Concrete implementation of deflate compression
/// </summary> /// </summary>
internal class DeflateCompression : ICompression internal class DeflateCompression : ICompression
{ {
public async Task<byte[]> Compress(byte[] responseBody) public Stream GetStream(Stream stream)
{ {
using (var ms = new MemoryStream()) return new DeflateStream(stream, CompressionMode.Compress, true);
{
using (var zip = new DeflateStream(ms, CompressionMode.Compress, true))
{
await zip.WriteAsync(responseBody, 0, responseBody.Length);
}
return ms.ToArray();
}
} }
} }
} }
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression namespace Titanium.Web.Proxy.Compression
{ {
/// <summary> /// <summary>
/// concreate implementation of gzip compression /// concreate implementation of gzip compression
/// </summary> /// </summary>
internal class GZipCompression : ICompression internal class GZipCompression : ICompression
{ {
public async Task<byte[]> Compress(byte[] responseBody) public Stream GetStream(Stream stream)
{ {
using (var ms = new MemoryStream()) return new GZipStream(stream, CompressionMode.Compress, true);
{
using (var zip = new GZipStream(ms, CompressionMode.Compress, true))
{
await zip.WriteAsync(responseBody, 0, responseBody.Length);
}
return ms.ToArray();
}
} }
} }
} }
using System.Threading.Tasks; using System.IO;
namespace Titanium.Web.Proxy.Compression namespace Titanium.Web.Proxy.Compression
{ {
/// <summary> /// <summary>
/// An inteface for http compression /// An inteface for http compression
/// </summary> /// </summary>
interface ICompression internal interface ICompression
{ {
Task<byte[]> Compress(byte[] responseBody); Stream GetStream(Stream stream);
} }
} }
namespace Titanium.Web.Proxy.Decompression using System;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Decompression
{ {
/// <summary> /// <summary>
/// A factory to generate the de-compression methods based on the type of compression /// A factory to generate the de-compression methods based on the type of compression
/// </summary> /// </summary>
internal class DecompressionFactory internal class DecompressionFactory
{ {
internal IDecompression Create(string type) //cache
private static readonly IDecompression gzip = new GZipDecompression();
private static readonly IDecompression deflate = new DeflateDecompression();
internal static IDecompression Create(string type)
{ {
switch (type) switch (type)
{ {
case "gzip": case KnownHeaders.ContentEncodingGzip:
return new GZipDecompression(); return gzip;
case "deflate": case KnownHeaders.ContentEncodingDeflate:
return new DeflateDecompression(); return deflate;
default: default:
return new DefaultDecompression(); throw new Exception($"Unsupported decompression mode: {type}");
} }
} }
} }
......
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Decompression
{
/// <summary>
/// When no compression is specified just return the byte array
/// </summary>
internal class DefaultDecompression : IDecompression
{
public Task<byte[]> Decompress(byte[] compressedArray, int bufferSize)
{
return Task.FromResult(compressedArray);
}
}
}
using StreamExtended.Helpers; using System.IO;
using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
/// <summary> /// <summary>
/// concrete implementation of deflate de-compression /// concrete implementation of deflate de-compression
/// </summary> /// </summary>
internal class DeflateDecompression : IDecompression internal class DeflateDecompression : IDecompression
{ {
public async Task<byte[]> Decompress(byte[] compressedArray, int bufferSize) public Stream GetStream(Stream stream)
{ {
using (var stream = new MemoryStream(compressedArray)) return new DeflateStream(stream, CompressionMode.Decompress, true);
using (var decompressor = new DeflateStream(stream, CompressionMode.Decompress))
{
var buffer = BufferPool.GetBuffer(bufferSize);
try
{
using (var output = new MemoryStream())
{
int read;
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
output.Write(buffer, 0, read);
}
return output.ToArray();
}
}
finally
{
BufferPool.ReturnBuffer(buffer);
}
}
} }
} }
} }
using StreamExtended.Helpers; using System.IO;
using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
/// <summary> /// <summary>
/// concrete implementation of gzip de-compression /// concrete implementation of gzip de-compression
/// </summary> /// </summary>
internal class GZipDecompression : IDecompression internal class GZipDecompression : IDecompression
{ {
public async Task<byte[]> Decompress(byte[] compressedArray, int bufferSize) public Stream GetStream(Stream stream)
{ {
using (var decompressor = new GZipStream(new MemoryStream(compressedArray), CompressionMode.Decompress)) return new GZipStream(stream, CompressionMode.Decompress, true);
{
var buffer = BufferPool.GetBuffer(bufferSize);
try
{
using (var output = new MemoryStream())
{
int read;
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
output.Write(buffer, 0, read);
}
return output.ToArray();
}
}
finally
{
BufferPool.ReturnBuffer(buffer);
}
}
} }
} }
} }
using System.Threading.Tasks; using System.IO;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
/// <summary> /// <summary>
/// An interface for decompression /// An interface for decompression
/// </summary> /// </summary>
internal interface IDecompression internal interface IDecompression
{ {
Task<byte[]> Decompress(byte[] compressedArray, int bufferSize); Stream GetStream(Stream stream);
} }
} }
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.EventArguments
{
/// <summary>
/// A generic asynchronous event handler used by the proxy.
/// </summary>
/// <typeparam name="TEventArgs">Event argument type.</typeparam>
/// <param name="sender">The proxy server instance.</param>
/// <param name="e">The event arguments.</param>
/// <returns></returns>
public delegate Task AsyncEventHandler<in TEventArgs>(object sender, TEventArgs e);
}
using System;
using System.Threading;
namespace Titanium.Web.Proxy.EventArguments
{
/// <summary>
/// This is used in transparent endpoint before authenticating client.
/// </summary>
public class BeforeSslAuthenticateEventArgs : EventArgs
{
internal readonly CancellationTokenSource TaskCancellationSource;
internal BeforeSslAuthenticateEventArgs(CancellationTokenSource taskCancellationSource)
{
TaskCancellationSource = taskCancellationSource;
}
/// <summary>
/// The server name indication hostname if available. Otherwise the generic certificate hostname of
/// TransparentEndPoint.
/// </summary>
public string SniHostName { get; internal set; }
/// <summary>
/// Should we decrypt the SSL request?
/// If true we decrypt with fake certificate.
/// If false we relay the connection to the hostname mentioned in SniHostname.
/// </summary>
public bool DecryptSsl { get; set; } = true;
/// <summary>
/// Terminate the request abruptly by closing client/server connections.
/// </summary>
public void TerminateSession()
{
TaskCancellationSource.Cancel();
}
}
}
...@@ -4,37 +4,37 @@ using System.Security.Cryptography.X509Certificates; ...@@ -4,37 +4,37 @@ using System.Security.Cryptography.X509Certificates;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
/// <summary> /// <summary>
/// An argument passed on to user for client certificate selection during mutual SSL authentication /// An argument passed on to user for client certificate selection during mutual SSL authentication.
/// </summary> /// </summary>
public class CertificateSelectionEventArgs : EventArgs public class CertificateSelectionEventArgs : EventArgs
{ {
/// <summary> /// <summary>
/// Sender object. /// The proxy server instance.
/// </summary> /// </summary>
public object Sender { get; internal set; } public object Sender { get; internal set; }
/// <summary> /// <summary>
/// Target host. /// The remote hostname to which we are authenticating against.
/// </summary> /// </summary>
public string TargetHost { get; internal set; } public string TargetHost { get; internal set; }
/// <summary> /// <summary>
/// Local certificates. /// Local certificates in store with matching issuers requested by TargetHost website.
/// </summary> /// </summary>
public X509CertificateCollection LocalCertificates { get; internal set; } public X509CertificateCollection LocalCertificates { get; internal set; }
/// <summary> /// <summary>
/// Remote certificate. /// Certificate of the remote server.
/// </summary> /// </summary>
public X509Certificate RemoteCertificate { get; internal set; } public X509Certificate RemoteCertificate { get; internal set; }
/// <summary> /// <summary>
/// Acceptable issuers. /// Acceptable issuers as listed by remoted server.
/// </summary> /// </summary>
public string[] AcceptableIssuers { get; internal set; } public string[] AcceptableIssuers { get; internal set; }
/// <summary> /// <summary>
/// Client Certificate. /// Client Certificate we selected. Set this value to override.
/// </summary> /// </summary>
public X509Certificate ClientCertificate { get; set; } public X509Certificate ClientCertificate { get; set; }
} }
......
...@@ -5,27 +5,28 @@ using System.Security.Cryptography.X509Certificates; ...@@ -5,27 +5,28 @@ using System.Security.Cryptography.X509Certificates;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
/// <summary> /// <summary>
/// An argument passed on to the user for validating the server certificate during SSL authentication /// An argument passed on to the user for validating the server certificate
/// during SSL authentication.
/// </summary> /// </summary>
public class CertificateValidationEventArgs : EventArgs public class CertificateValidationEventArgs : EventArgs
{ {
/// <summary> /// <summary>
/// Certificate /// Server certificate.
/// </summary> /// </summary>
public X509Certificate Certificate { get; internal set; } public X509Certificate Certificate { get; internal set; }
/// <summary> /// <summary>
/// Certificate chain /// Certificate chain.
/// </summary> /// </summary>
public X509Chain Chain { get; internal set; } public X509Chain Chain { get; internal set; }
/// <summary> /// <summary>
/// SSL policy errors. /// SSL policy errors.
/// </summary> /// </summary>
public SslPolicyErrors SslPolicyErrors { get; internal set; } public SslPolicyErrors SslPolicyErrors { get; internal set; }
/// <summary> /// <summary>
/// is a valid certificate? /// Is the given server certificate valid?
/// </summary> /// </summary>
public bool IsValid { get; set; } public bool IsValid { get; set; }
} }
......
...@@ -2,19 +2,31 @@ using System; ...@@ -2,19 +2,31 @@ using System;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
/// <summary>
/// Wraps the data sent/received by a proxy server instance.
/// </summary>
public class DataEventArgs : EventArgs public class DataEventArgs : EventArgs
{ {
public byte[] Buffer { get; } internal DataEventArgs(byte[] buffer, int offset, int count)
public int Offset { get; }
public int Count { get; }
public DataEventArgs(byte[] buffer, int offset, int count)
{ {
Buffer = buffer; Buffer = buffer;
Offset = offset; Offset = offset;
Count = count; Count = count;
} }
/// <summary>
/// The buffer with data.
/// </summary>
public byte[] Buffer { get; }
/// <summary>
/// Offset in buffer from which valid data begins.
/// </summary>
public int Offset { get; }
/// <summary>
/// Length from offset in buffer with valid data.
/// </summary>
public int Count { get; }
} }
} }
\ No newline at end of file
using System;
using System.Globalization;
using System.IO;
using System.Threading.Tasks;
using StreamExtended.Helpers;
using StreamExtended.Network;
namespace Titanium.Web.Proxy.EventArguments
{
internal class LimitedStream : Stream
{
private readonly CustomBinaryReader baseReader;
private readonly CustomBufferedStream baseStream;
private readonly bool isChunked;
private long bytesRemaining;
private bool readChunkTrail;
internal LimitedStream(CustomBufferedStream baseStream, CustomBinaryReader baseReader, bool isChunked,
long contentLength)
{
this.baseStream = baseStream;
this.baseReader = baseReader;
this.isChunked = isChunked;
bytesRemaining = isChunked
? 0
: contentLength == -1
? long.MaxValue
: contentLength;
}
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => throw new NotSupportedException();
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
private void GetNextChunk()
{
if (readChunkTrail)
{
// read the chunk trail of the previous chunk
string s = baseReader.ReadLineAsync().Result;
}
readChunkTrail = true;
string chunkHead = baseReader.ReadLineAsync().Result;
int idx = chunkHead.IndexOf(";", StringComparison.Ordinal);
if (idx >= 0)
{
chunkHead = chunkHead.Substring(0, idx);
}
int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
bytesRemaining = chunkSize;
if (chunkSize == 0)
{
bytesRemaining = -1;
//chunk trail
baseReader.ReadLineAsync().Wait();
}
}
public override void Flush()
{
throw new NotSupportedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
if (bytesRemaining == -1)
{
return 0;
}
if (bytesRemaining == 0)
{
if (isChunked)
{
GetNextChunk();
}
else
{
bytesRemaining = -1;
}
}
if (bytesRemaining == -1)
{
return 0;
}
int toRead = (int)Math.Min(count, bytesRemaining);
int res = baseStream.Read(buffer, offset, toRead);
bytesRemaining -= res;
if (res == 0)
{
bytesRemaining = -1;
}
return res;
}
public async Task Finish()
{
if (bytesRemaining != -1)
{
var buffer = BufferPool.GetBuffer(baseReader.Buffer.Length);
try
{
int res = await ReadAsync(buffer, 0, buffer.Length);
if (res != 0)
{
throw new Exception("Data received after stream end");
}
}
finally
{
BufferPool.ReturnBuffer(buffer);
}
}
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
}
}
using System;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.EventArguments
{
/// <summary>
/// Class that wraps the multipart sent request arguments.
/// </summary>
public class MultipartRequestPartSentEventArgs : EventArgs
{
internal MultipartRequestPartSentEventArgs(string boundary, HeaderCollection headers)
{
Boundary = boundary;
Headers = headers;
}
/// <summary>
/// Boundary.
/// </summary>
public string Boundary { get; }
/// <summary>
/// The header collection.
/// </summary>
public HeaderCollection Headers { get; }
}
}
using System;
using System.Net;
using System.Threading;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
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 abstract class SessionEventArgsBase : EventArgs, IDisposable
{
/// <summary>
/// Size of Buffers used by this object
/// </summary>
protected readonly int BufferSize;
internal readonly CancellationTokenSource CancellationTokenSource;
protected readonly ExceptionHandler ExceptionFunc;
/// <summary>
/// Constructor to initialize the proxy
/// </summary>
internal SessionEventArgsBase(int bufferSize, ProxyEndPoint endPoint,
CancellationTokenSource cancellationTokenSource, ExceptionHandler exceptionFunc)
: this(bufferSize, endPoint, cancellationTokenSource, null, exceptionFunc)
{
}
protected SessionEventArgsBase(int bufferSize, ProxyEndPoint endPoint,
CancellationTokenSource cancellationTokenSource,
Request request, ExceptionHandler exceptionFunc)
{
BufferSize = bufferSize;
ExceptionFunc = exceptionFunc;
CancellationTokenSource = cancellationTokenSource;
ProxyClient = new ProxyClient();
WebSession = new HttpWebClient(bufferSize, request);
LocalEndPoint = endPoint;
WebSession.ProcessId = new Lazy<int>(() =>
{
if (RunTime.IsWindows)
{
var remoteEndPoint = (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint;
//If client is localhost get the process id
if (NetworkHelper.IsLocalIpAddress(remoteEndPoint.Address))
{
var ipVersion = endPoint.IpV6Enabled ? IpVersion.Ipv6 : IpVersion.Ipv4;
return TcpHelper.GetProcessIdByLocalPort(ipVersion, remoteEndPoint.Port);
}
//can't access process Id of remote request from remote machine
return -1;
}
throw new PlatformNotSupportedException();
});
}
/// <summary>
/// Holds a reference to client
/// </summary>
internal ProxyClient ProxyClient { get; }
/// <summary>
/// Returns a unique Id for this request/response session which is
/// same as the RequestId of WebSession.
/// </summary>
public Guid Id => WebSession.RequestId;
/// <summary>
/// Does this session uses SSL?
/// </summary>
public bool IsHttps => WebSession.Request.IsHttps;
/// <summary>
/// Client End Point.
/// </summary>
public IPEndPoint ClientEndPoint => (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint;
/// <summary>
/// A web session corresponding to a single request/response sequence
/// within a proxy connection.
/// </summary>
public HttpWebClient WebSession { get; }
/// <summary>
/// Are we using a custom upstream HTTP(S) proxy?
/// </summary>
public ExternalProxy CustomUpStreamProxyUsed { get; internal set; }
/// <summary>
/// Local endpoint via which we make the request.
/// </summary>
public ProxyEndPoint LocalEndPoint { get; }
/// <summary>
/// Is this a transparent endpoint?
/// </summary>
public bool IsTransparent => LocalEndPoint is TransparentProxyEndPoint;
/// <summary>
/// The last exception that happened.
/// </summary>
public Exception Exception { get; internal set; }
/// <summary>
/// Implements cleanup here.
/// </summary>
public virtual void Dispose()
{
CustomUpStreamProxyUsed = null;
DataSent = null;
DataReceived = null;
Exception = null;
WebSession.FinishSession();
}
/// <summary>
/// Fired when data is sent within this session to server/client.
/// </summary>
public event EventHandler<DataEventArgs> DataSent;
/// <summary>
/// Fired when data is received within this session from client/server.
/// </summary>
public event EventHandler<DataEventArgs> DataReceived;
internal void OnDataSent(byte[] buffer, int offset, int count)
{
try
{
DataSent?.Invoke(this, new DataEventArgs(buffer, offset, count));
}
catch (Exception ex)
{
ExceptionFunc(new Exception("Exception thrown in user event", ex));
}
}
internal void OnDataReceived(byte[] buffer, int offset, int count)
{
try
{
DataReceived?.Invoke(this, new DataEventArgs(buffer, offset, count));
}
catch (Exception ex)
{
ExceptionFunc(new Exception("Exception thrown in user event", ex));
}
}
/// <summary>
/// Terminates the session abruptly by terminating client/server connections.
/// </summary>
public void TerminateSession()
{
CancellationTokenSource.Cancel();
}
}
}
namespace Titanium.Web.Proxy.EventArguments
{
internal enum TransformationMode
{
None,
/// <summary>
/// Removes the chunked encoding
/// </summary>
RemoveChunked,
/// <summary>
/// Uncompress the body (this also removes the chunked encoding if exists)
/// </summary>
Uncompress
}
}
using System.Net; using System;
using System.Threading;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
public class TunnelConnectSessionEventArgs : SessionEventArgs /// <summary>
/// A class that wraps the state when a tunnel connect event happen for Explicit endpoints.
/// </summary>
public class TunnelConnectSessionEventArgs : SessionEventArgsBase
{ {
public bool IsHttpsConnect { get; set; } private bool? isHttpsConnect;
internal TunnelConnectSessionEventArgs(int bufferSize, ProxyEndPoint endPoint, ConnectRequest connectRequest) internal TunnelConnectSessionEventArgs(int bufferSize, ProxyEndPoint endPoint, ConnectRequest connectRequest,
: base(bufferSize, endPoint, null) CancellationTokenSource cancellationTokenSource, ExceptionHandler exceptionFunc)
: base(bufferSize, endPoint, cancellationTokenSource, connectRequest, exceptionFunc)
{ {
WebSession.Request = connectRequest; WebSession.ConnectRequest = connectRequest;
}
/// <summary>
/// Should we decrypt the Ssl or relay it to server?
/// Default is true.
/// </summary>
public bool DecryptSsl { get; set; } = true;
/// <summary>
/// When set to true it denies the connect request with a Forbidden status.
/// </summary>
public bool DenyConnect { get; set; }
/// <summary>
/// Is this a connect request to secure HTTP server? Or is it to someother protocol.
/// </summary>
public bool IsHttpsConnect
{
get => isHttpsConnect ??
throw new Exception("The value of this property is known in the BeforeTunnectConnectResponse event");
internal set => isHttpsConnect = value;
} }
} }
} }
using System;
namespace Titanium.Web.Proxy
{
/// <summary>
/// A delegate to catch exceptions occuring in proxy.
/// </summary>
/// <param name="exception">The exception occurred in proxy.</param>
public delegate void ExceptionHandler(Exception exception);
}
namespace Titanium.Web.Proxy.Exceptions namespace Titanium.Web.Proxy.Exceptions
{ {
/// <summary> /// <summary>
/// An expception thrown when body is unexpectedly empty /// An exception thrown when body is unexpectedly empty.
/// </summary> /// </summary>
public class BodyNotFoundException : ProxyException public class BodyNotFoundException : ProxyException
{ {
/// <summary> /// <summary>
/// Constructor. /// Constructor.
/// </summary> /// </summary>
/// <param name="message"></param> /// <param name="message"></param>
public BodyNotFoundException(string message) : base(message) internal BodyNotFoundException(string message) : base(message)
{ {
} }
} }
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Exceptions namespace Titanium.Web.Proxy.Exceptions
{ {
/// <summary> /// <summary>
/// Proxy authorization exception /// Proxy authorization exception.
/// </summary> /// </summary>
public class ProxyAuthorizationException : ProxyException public class ProxyAuthorizationException : ProxyException
{ {
/// <summary> /// <summary>
/// Instantiate new instance /// Instantiate new instance.
/// </summary> /// </summary>
/// <param name="message">Exception message</param> /// <param name="message">Exception message.</param>
/// <param name="session">The <see cref="SessionEventArgs" /> instance containing the event data.</param>
/// <param name="innerException">Inner exception associated to upstream proxy authorization</param> /// <param name="innerException">Inner exception associated to upstream proxy authorization</param>
/// <param name="headers">Http's headers associated</param> /// <param name="headers">Http's headers associated</param>
public ProxyAuthorizationException(string message, Exception innerException, IEnumerable<HttpHeader> headers) : base(message, innerException) internal ProxyAuthorizationException(string message, SessionEventArgsBase session, Exception innerException,
IEnumerable<HttpHeader> headers) : base(message, innerException)
{ {
Session = session;
Headers = headers; Headers = headers;
} }
/// <summary> /// <summary>
/// Headers associated with the authorization exception /// The current session within which this error happened.
/// </summary>
public SessionEventArgsBase Session { get; }
/// <summary>
/// Headers associated with the authorization exception.
/// </summary> /// </summary>
public IEnumerable<HttpHeader> Headers { get; } public IEnumerable<HttpHeader> Headers { get; }
} }
......
...@@ -3,12 +3,12 @@ ...@@ -3,12 +3,12 @@
namespace Titanium.Web.Proxy.Exceptions namespace Titanium.Web.Proxy.Exceptions
{ {
/// <summary> /// <summary>
/// Base class exception associated with this proxy implementation /// Base class exception associated with this proxy server.
/// </summary> /// </summary>
public abstract class ProxyException : Exception public abstract class ProxyException : Exception
{ {
/// <summary> /// <summary>
/// Instantiate a new instance of this exception - must be invoked by derived classes' constructors /// Instantiate a new instance of this exception - must be invoked by derived classes' constructors
/// </summary> /// </summary>
/// <param name="message">Exception message</param> /// <param name="message">Exception message</param>
protected ProxyException(string message) : base(message) protected ProxyException(string message) : base(message)
...@@ -16,7 +16,7 @@ namespace Titanium.Web.Proxy.Exceptions ...@@ -16,7 +16,7 @@ namespace Titanium.Web.Proxy.Exceptions
} }
/// <summary> /// <summary>
/// Instantiate this exception - must be invoked by derived classes' constructors /// Instantiate this exception - must be invoked by derived classes' constructors
/// </summary> /// </summary>
/// <param name="message">Excception message</param> /// <param name="message">Excception message</param>
/// <param name="innerException">Inner exception associated</param> /// <param name="innerException">Inner exception associated</param>
......
...@@ -4,26 +4,27 @@ using Titanium.Web.Proxy.EventArguments; ...@@ -4,26 +4,27 @@ using Titanium.Web.Proxy.EventArguments;
namespace Titanium.Web.Proxy.Exceptions namespace Titanium.Web.Proxy.Exceptions
{ {
/// <summary> /// <summary>
/// Proxy HTTP exception /// Proxy HTTP exception.
/// </summary> /// </summary>
public class ProxyHttpException : ProxyException public class ProxyHttpException : ProxyException
{ {
/// <summary> /// <summary>
/// Instantiate new instance /// Instantiate new instance
/// </summary> /// </summary>
/// <param name="message">Message for this exception</param> /// <param name="message">Message for this exception</param>
/// <param name="innerException">Associated inner exception</param> /// <param name="innerException">Associated inner exception</param>
/// <param name="sessionEventArgs">Instance of <see cref="EventArguments.SessionEventArgs"/> associated to the exception</param> /// <param name="sessionEventArgs">Instance of <see cref="EventArguments.SessionEventArgs" /> associated to the exception</param>
public ProxyHttpException(string message, Exception innerException, SessionEventArgs sessionEventArgs) : base(message, innerException) internal ProxyHttpException(string message, Exception innerException, SessionEventArgs sessionEventArgs) : base(
message, innerException)
{ {
SessionEventArgs = sessionEventArgs; SessionEventArgs = sessionEventArgs;
} }
/// <summary> /// <summary>
/// Gets session info associated to the exception /// Gets session info associated to the exception.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This object should not be edited /// This object properties should not be edited.
/// </remarks> /// </remarks>
public SessionEventArgs SessionEventArgs { get; } public SessionEventArgs SessionEventArgs { get; }
} }
......
This diff is collapsed.
using System;
namespace Titanium.Web.Proxy.Extensions
{
/// <summary>
/// Extension methods for Byte Arrays.
/// </summary>
internal static class ByteArrayExtensions
{
/// <summary>
/// Get the sub array from byte of data
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="data"></param>
/// <param name="index"></param>
/// <param name="length"></param>
/// <returns></returns>
internal static T[] SubArray<T>(this T[] data, int index, int length)
{
var result = new T[length];
Array.Copy(data, index, result, 0, length);
return result;
}
}
}
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
internal static class FuncExtensions internal static class FuncExtensions
{ {
public static void InvokeParallel<T>(this Func<object, T, Task> callback, object sender, T args) internal static async Task InvokeAsync<T>(this AsyncEventHandler<T> callback, object sender, T args,
ExceptionHandler exceptionFunc)
{ {
var invocationList = callback.GetInvocationList(); var invocationList = callback.GetInvocationList();
var handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++) foreach (var @delegate in invocationList)
{ {
handlerTasks[i] = ((Func<object, T, Task>)invocationList[i])(sender, args); await InternalInvokeAsync((AsyncEventHandler<T>)@delegate, sender, args, exceptionFunc);
} }
Task.WhenAll(handlerTasks).Wait();
}
public static async Task InvokeParallelAsync<T>(this Func<object, T, Task> callback, object sender, T args, Action<Exception> exceptionFunc)
{
var invocationList = callback.GetInvocationList();
var handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = InvokeAsync((Func<object, T, Task>)invocationList[i], sender, args, exceptionFunc);
}
await Task.WhenAll(handlerTasks);
} }
private static async Task InvokeAsync<T>(Func<object, T, Task> callback, object sender, T args, Action<Exception> exceptionFunc) private static async Task InternalInvokeAsync<T>(AsyncEventHandler<T> callback, object sender, T args,
ExceptionHandler exceptionFunc)
{ {
try try
{ {
await callback(sender, args); await callback(sender, args);
} }
catch (Exception ex) catch (Exception e)
{ {
var ex2 = new Exception("Exception thrown in user event", ex); exceptionFunc(new Exception("Exception thrown in user event", e));
exceptionFunc(ex2);
} }
} }
} }
......
using System.Text;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Extensions
{
/// <summary>
/// Extensions on HttpWebSession object
/// </summary>
internal static class HttpWebRequestExtensions
{
/// <summary>
/// parse the character encoding of request from request headers
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
internal static Encoding GetEncoding(this Request request)
{
return HttpHelper.GetEncodingFromContentType(request.ContentType);
}
}
}
using System.Text;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Extensions
{
internal static class HttpWebResponseExtensions
{
/// <summary>
/// Gets the character encoding of response from response headers
/// </summary>
/// <param name="response"></param>
/// <returns></returns>
internal static Encoding GetResponseCharacterEncoding(this Response response)
{
return HttpHelper.GetEncodingFromContentType(response.ContentType);
}
}
}
This diff is collapsed.
using System.Globalization; using System;
using System.Globalization;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
internal static class StringExtensions internal static class StringExtensions
{ {
internal static bool EqualsIgnoreCase(this string str, string value)
{
return str.Equals(value, StringComparison.CurrentCultureIgnoreCase);
}
internal static bool ContainsIgnoreCase(this string str, string value) internal static bool ContainsIgnoreCase(this string str, string value)
{ {
return CultureInfo.CurrentCulture.CompareInfo.IndexOf(str, value, CompareOptions.IgnoreCase) >= 0; return CultureInfo.CurrentCulture.CompareInfo.IndexOf(str, value, CompareOptions.IgnoreCase) >= 0;
} }
internal static int IndexOfIgnoreCase(this string str, string value)
{
return CultureInfo.CurrentCulture.CompareInfo.IndexOf(str, value, CompareOptions.IgnoreCase);
}
} }
} }
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -9,11 +9,11 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp ...@@ -9,11 +9,11 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
{ {
} }
public override bool IsInvalid => handle == IntPtr.Zero;
protected override bool ReleaseHandle() protected override bool ReleaseHandle()
{ {
return NativeMethods.WinHttp.WinHttpCloseHandle(handle); return NativeMethods.WinHttp.WinHttpCloseHandle(handle);
} }
public override bool IsInvalid => handle == IntPtr.Zero;
} }
} }
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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