Commit c44cc790 authored by Jehonathan Thomas's avatar Jehonathan Thomas Committed by GitHub

Merge pull request #295 from justcoding121/develop

Beta release
parents ac2877be 103754dc
No preview for this file type
...@@ -25,8 +25,7 @@ namespace Titanium.Web.Proxy.Examples.Basic.Helpers ...@@ -25,8 +25,7 @@ namespace Titanium.Web.Proxy.Examples.Basic.Helpers
internal static bool DisableQuickEditMode() internal static bool DisableQuickEditMode()
{ {
var consoleHandle = GetStdHandle(STD_INPUT_HANDLE);
IntPtr consoleHandle = GetStdHandle(STD_INPUT_HANDLE);
// get current console mode // get current console mode
uint consoleMode; uint consoleMode;
......
using System; using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Titanium.Web.Proxy.Examples.Basic.Helpers; using Titanium.Web.Proxy.Examples.Basic.Helpers;
namespace Titanium.Web.Proxy.Examples.Basic namespace Titanium.Web.Proxy.Examples.Basic
......
...@@ -10,7 +10,7 @@ using System.Runtime.InteropServices; ...@@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")] [assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Demo")] [assembly: AssemblyProduct("Demo")]
[assembly: AssemblyCopyright("Copyright ©2013 Telerik")] [assembly: AssemblyCopyright("Copyright © Titanium 2015-2017")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
......
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Net.Security; using System.Net.Security;
...@@ -17,8 +16,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -17,8 +16,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
//share requestBody outside handlers //share requestBody outside handlers
//Using a dictionary is not a good idea since it can cause memory overflow //Using a dictionary is not a good idea since it can cause memory overflow
//ideally the data should be moved out of memory //ideally the data should be moved out of memory
//private readonly IDictionary<Guid, string> requestBodyHistory //private readonly IDictionary<Guid, string> requestBodyHistory = new ConcurrentDictionary<Guid, string>();
// = new ConcurrentDictionary<Guid, string>();
public ProxyTestController() public ProxyTestController()
{ {
...@@ -45,6 +43,8 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -45,6 +43,8 @@ namespace Titanium.Web.Proxy.Examples.Basic
{ {
proxyServer.BeforeRequest += OnRequest; proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse; proxyServer.BeforeResponse += OnResponse;
proxyServer.TunnelConnectRequest += OnTunnelConnectRequest;
proxyServer.TunnelConnectResponse += OnTunnelConnectResponse;
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation; proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection; proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
...@@ -55,11 +55,17 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -55,11 +55,17 @@ namespace Titanium.Web.Proxy.Examples.Basic
//Exclude Https addresses you don't want to proxy //Exclude Https addresses you don't want to proxy
//Useful for clients that use certificate pinning //Useful for clients that use certificate pinning
//for example google.com and dropbox.com //for example google.com and dropbox.com
ExcludedHttpsHostNameRegex = new List<string> { "dropbox.com" } ExcludedHttpsHostNameRegex = new List<string>
{
"dropbox.com"
},
//Include Https addresses you want to proxy (others will be excluded) //Include Https addresses you want to proxy (others will be excluded)
//for example github.com //for example github.com
//IncludedHttpsHostNameRegex = new List<string> { "github.com" } //IncludedHttpsHostNameRegex = new List<string>
//{
// "github.com"
//},
//You can set only one of the ExcludedHttpsHostNameRegex and IncludedHttpsHostNameRegex properties, otherwise ArgumentException will be thrown //You can set only one of the ExcludedHttpsHostNameRegex and IncludedHttpsHostNameRegex properties, otherwise ArgumentException will be thrown
...@@ -75,24 +81,23 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -75,24 +81,23 @@ namespace Titanium.Web.Proxy.Examples.Basic
proxyServer.Start(); proxyServer.Start();
//Transparent endpoint is useful for reverse proxying (client is not aware of the existence of proxy) //Transparent endpoint is useful for reverse proxy (client is not aware of the existence of proxy)
//A transparent endpoint usually requires a network router port forwarding HTTP(S) packets to this endpoint //A transparent endpoint usually requires a network router port forwarding HTTP(S) packets or DNS
//Currently do not support Server Name Indication (It is not currently supported by SslStream class) //to send data to this endPoint
//That means that the transparent endpoint will always provide the same Generic Certificate to all HTTPS requests //var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 443, true)
//In this example only google.com will work for HTTPS requests //{
//Other sites will receive a certificate mismatch warning on browser // //Generic Certificate hostname to use
var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true) // //When SNI is disabled by client
{ // GenericCertificateName = "google.com"
GenericCertificateName = "google.com" //};
};
proxyServer.AddEndPoint(transparentEndPoint); //proxyServer.AddEndPoint(transparentEndPoint);
//proxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 }; //proxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//proxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 }; //proxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
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);
//Only explicit proxies can be set as system proxy! //Only explicit proxies can be set as system proxy!
//proxyServer.SetAsSystemHttpProxy(explicitEndPoint); //proxyServer.SetAsSystemHttpProxy(explicitEndPoint);
...@@ -102,6 +107,8 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -102,6 +107,8 @@ namespace Titanium.Web.Proxy.Examples.Basic
public void Stop() public void Stop()
{ {
proxyServer.TunnelConnectRequest -= OnTunnelConnectRequest;
proxyServer.TunnelConnectResponse -= OnTunnelConnectResponse;
proxyServer.BeforeRequest -= OnRequest; proxyServer.BeforeRequest -= OnRequest;
proxyServer.BeforeResponse -= OnResponse; proxyServer.BeforeResponse -= OnResponse;
proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation; proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation;
...@@ -113,6 +120,15 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -113,6 +120,15 @@ namespace Titanium.Web.Proxy.Examples.Basic
//proxyServer.CertificateManager.RemoveTrustedRootCertificates(); //proxyServer.CertificateManager.RemoveTrustedRootCertificates();
} }
private async Task OnTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
{
Console.WriteLine("Tunnel to: " + e.WebSession.Request.Host);
}
private async Task OnTunnelConnectResponse(object sender, TunnelConnectSessionEventArgs e)
{
}
//intecept & cancel redirect or update requests //intecept & cancel redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e) public async Task OnRequest(object sender, SessionEventArgs e)
{ {
...@@ -125,7 +141,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -125,7 +141,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
if (e.WebSession.Request.HasBody) if (e.WebSession.Request.HasBody)
{ {
//Get/Set request body bytes //Get/Set request body bytes
byte[] bodyBytes = await e.GetRequestBody(); var bodyBytes = await e.GetRequestBody();
await e.SetRequestBody(bodyBytes); await e.SetRequestBody(bodyBytes);
//Get/Set request body as string //Get/Set request body as string
...@@ -175,11 +191,11 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -175,11 +191,11 @@ namespace Titanium.Web.Proxy.Examples.Basic
//if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return; //if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST") if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST")
{ {
if (e.WebSession.Response.ResponseStatusCode == "200") if (e.WebSession.Response.ResponseStatusCode == (int)HttpStatusCode.OK)
{ {
if (e.WebSession.Response.ContentType != null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html")) if (e.WebSession.Response.ContentType != null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html"))
{ {
byte[] bodyBytes = await e.GetResponseBody(); var bodyBytes = await e.GetResponseBody();
await e.SetResponseBody(bodyBytes); await e.SetResponseBody(bodyBytes);
string body = await e.GetResponseBodyAsString(); string body = await e.GetResponseBodyAsString();
......
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
</startup>
</configuration>
<Application x:Class="Titanium.Web.Proxy.Examples.Wpf.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Titanium.Web.Proxy.Examples.Wpf"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace Titanium.Web.Proxy.Examples.Wpf
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}
<Window x:Class="Titanium.Web.Proxy.Examples.Wpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Titanium.Web.Proxy.Examples.Wpf"
mc:Ignorable="d"
Title="MainWindow" Height="500" Width="1000" WindowState="Maximized"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="500" />
<ColumnDefinition Width="3" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<GridSplitter Grid.Column="1" Grid.Row="0" HorizontalAlignment="Stretch" />
<ListView Grid.Column="0" Grid.Row="0" HorizontalAlignment="Stretch" ItemsSource="{Binding Sessions}" SelectedItem="{Binding SelectedSession}"
KeyDown="ListViewSessions_OnKeyDown">
<ListView.View>
<GridView>
<GridViewColumn Header="Result" DisplayMemberBinding="{Binding StatusCode}" />
<GridViewColumn Header="Protocol" DisplayMemberBinding="{Binding Protocol}" />
<GridViewColumn Header="Host" DisplayMemberBinding="{Binding Host}" />
<GridViewColumn Header="Url" DisplayMemberBinding="{Binding Url}" />
<GridViewColumn Header="BodySize" DisplayMemberBinding="{Binding BodySize}" />
<GridViewColumn Header="Process" DisplayMemberBinding="{Binding Process}" />
<GridViewColumn Header="SentBytes" DisplayMemberBinding="{Binding SentDataCount}" />
<GridViewColumn Header="ReceivedBytes" DisplayMemberBinding="{Binding ReceivedDataCount}" />
</GridView>
</ListView.View>
</ListView>
<TabControl Grid.Column="2" Grid.Row="0">
<TabItem Header="Session">
<Grid Background="Red" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox x:Name="TextBoxRequest" Grid.Row="0" />
<TextBox x:Name="TextBoxResponse" Grid.Row="1" />
</Grid>
</TabItem>
</TabControl>
<StackPanel Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="3" Orientation="Horizontal">
<TextBlock Text="ClientConnectionCount:" />
<TextBlock Text="{Binding ClientConnectionCount}" Margin="10,0,20,0" />
<TextBlock Text="ServerConnectionCount:" />
<TextBlock Text="{Binding ServerConnectionCount}" Margin="10,0,20,0" />
</StackPanel>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Examples.Wpf
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private readonly ProxyServer proxyServer;
private int lastSessionNumber;
public ObservableCollection<SessionListItem> Sessions { get; } = new ObservableCollection<SessionListItem>();
public SessionListItem SelectedSession
{
get { return selectedSession; }
set
{
if (value != selectedSession)
{
selectedSession = value;
SelectedSessionChanged();
}
}
}
public static readonly DependencyProperty ClientConnectionCountProperty = DependencyProperty.Register(
nameof(ClientConnectionCount), typeof(int), typeof(MainWindow), new PropertyMetadata(default(int)));
public int ClientConnectionCount
{
get { return (int)GetValue(ClientConnectionCountProperty); }
set { SetValue(ClientConnectionCountProperty, value); }
}
public static readonly DependencyProperty ServerConnectionCountProperty = DependencyProperty.Register(
nameof(ServerConnectionCount), typeof(int), typeof(MainWindow), new PropertyMetadata(default(int)));
public int ServerConnectionCount
{
get { return (int)GetValue(ServerConnectionCountProperty); }
set { SetValue(ServerConnectionCountProperty, value); }
}
private readonly Dictionary<SessionEventArgs, SessionListItem> sessionDictionary = new Dictionary<SessionEventArgs, SessionListItem>();
private SessionListItem selectedSession;
public MainWindow()
{
proxyServer = new ProxyServer();
proxyServer.TrustRootCertificate = true;
proxyServer.ForwardToUpstreamGateway = true;
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{
//IncludedHttpsHostNameRegex = new string[0],
};
proxyServer.AddEndPoint(explicitEndPoint);
//proxyServer.UpStreamHttpProxy = new ExternalProxy
//{
// HostName = "158.69.115.45",
// Port = 3128,
// UserName = "Titanium",
// Password = "Titanium",
//};
proxyServer.BeforeRequest += ProxyServer_BeforeRequest;
proxyServer.BeforeResponse += ProxyServer_BeforeResponse;
proxyServer.TunnelConnectRequest += ProxyServer_TunnelConnectRequest;
proxyServer.TunnelConnectResponse += ProxyServer_TunnelConnectResponse;
proxyServer.ClientConnectionCountChanged += delegate { Dispatcher.Invoke(() => { ClientConnectionCount = proxyServer.ClientConnectionCount; }); };
proxyServer.ServerConnectionCountChanged += delegate { Dispatcher.Invoke(() => { ServerConnectionCount = proxyServer.ServerConnectionCount; }); };
proxyServer.Start();
proxyServer.SetAsSystemProxy(explicitEndPoint, ProxyProtocolType.AllHttp);
InitializeComponent();
}
private async Task ProxyServer_TunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
{
await Dispatcher.InvokeAsync(() =>
{
AddSession(e);
});
}
private async Task ProxyServer_TunnelConnectResponse(object sender, SessionEventArgs e)
{
await Dispatcher.InvokeAsync(() =>
{
SessionListItem item;
if (sessionDictionary.TryGetValue(e, out item))
{
item.Response = e.WebSession.Response;
item.Update();
}
});
}
private async Task ProxyServer_BeforeRequest(object sender, SessionEventArgs e)
{
SessionListItem item = null;
await Dispatcher.InvokeAsync(() =>
{
item = AddSession(e);
});
if (e.WebSession.Request.HasBody)
{
item.RequestBody = await e.GetRequestBody();
}
}
private async Task ProxyServer_BeforeResponse(object sender, SessionEventArgs e)
{
SessionListItem item = null;
await Dispatcher.InvokeAsync(() =>
{
SessionListItem item2;
if (sessionDictionary.TryGetValue(e, out item2))
{
item2.Response = e.WebSession.Response;
item2.Update();
item = item2;
}
});
if (item != null)
{
if (e.WebSession.Response.HasBody)
{
item.ResponseBody = await e.GetResponseBody();
}
}
}
private SessionListItem AddSession(SessionEventArgs e)
{
var item = CreateSessionListItem(e);
Sessions.Add(item);
sessionDictionary.Add(e, item);
return item;
}
private SessionListItem CreateSessionListItem(SessionEventArgs e)
{
lastSessionNumber++;
var item = new SessionListItem
{
Number = lastSessionNumber,
SessionArgs = e,
// save the headers because TWP will set it to null in Dispose
RequestHeaders = e.WebSession.Request.RequestHeaders,
ResponseHeaders = e.WebSession.Response.ResponseHeaders,
Request = e.WebSession.Request,
Response = e.WebSession.Response,
};
if (e is TunnelConnectSessionEventArgs || e.WebSession.Request.UpgradeToWebSocket)
{
e.DataReceived += (sender, args) =>
{
var session = (SessionEventArgs)sender;
SessionListItem li;
if (sessionDictionary.TryGetValue(session, out li))
{
li.ReceivedDataCount += args.Count;
}
};
e.DataSent += (sender, args) =>
{
var session = (SessionEventArgs)sender;
SessionListItem li;
if (sessionDictionary.TryGetValue(session, out li))
{
li.SentDataCount += args.Count;
}
};
}
item.Update();
return item;
}
private void ListViewSessions_OnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Delete)
{
var selectedItems = ((ListView)sender).SelectedItems;
foreach (var item in selectedItems.Cast<SessionListItem>().ToArray())
{
Sessions.Remove(item);
sessionDictionary.Remove(item.SessionArgs);
}
}
}
private void SelectedSessionChanged()
{
if (SelectedSession == null)
{
return;
}
const int truncateLimit = 1024;
var session = SelectedSession;
var data = session.RequestBody ?? new byte[0];
bool truncated = data.Length > truncateLimit;
if (truncated)
{
data = data.Take(truncateLimit).ToArray();
}
//restore the headers
typeof(Request).GetProperty(nameof(Request.RequestHeaders)).SetValue(session.Request, session.RequestHeaders);
typeof(Response).GetProperty(nameof(Response.ResponseHeaders)).SetValue(session.Response, session.ResponseHeaders);
//string hexStr = string.Join(" ", data.Select(x => x.ToString("X2")));
TextBoxRequest.Text = session.Request.HeaderText + session.Request.Encoding.GetString(data) +
(truncated ? Environment.NewLine + $"Data is truncated after {truncateLimit} bytes" : null) +
(session.Request as ConnectRequest)?.ClientHelloInfo;
data = session.ResponseBody ?? new byte[0];
truncated = data.Length > truncateLimit;
if (truncated)
{
data = data.Take(truncateLimit).ToArray();
}
//hexStr = string.Join(" ", data.Select(x => x.ToString("X2")));
TextBoxResponse.Text = session.Response.HeaderText + session.Response.Encoding.GetString(data) +
(truncated ? Environment.NewLine + $"Data is truncated after {truncateLimit} bytes" : null) +
(session.Response as ConnectResponse)?.ServerHelloInfo;
}
}
}
/* MIT License
Copyright (c) 2016 JetBrains http://www.jetbrains.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. */
using System;
#pragma warning disable 1591
// ReSharper disable UnusedMember.Global
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedAutoPropertyAccessor.Global
// ReSharper disable IntroduceOptionalParameters.Global
// ReSharper disable MemberCanBeProtected.Global
// ReSharper disable InconsistentNaming
namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
{
/// <summary>
/// Indicates that the value of the marked element could be <c>null</c> sometimes,
/// so the check for <c>null</c> is necessary before its usage.
/// </summary>
/// <example><code>
/// [CanBeNull] object Test() => null;
///
/// void UseTest() {
/// var p = Test();
/// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException'
/// }
/// </code></example>
[AttributeUsage(
AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |
AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event |
AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)]
public sealed class CanBeNullAttribute : Attribute { }
/// <summary>
/// Indicates that the value of the marked element could never be <c>null</c>.
/// </summary>
/// <example><code>
/// [NotNull] object Foo() {
/// return null; // Warning: Possible 'null' assignment
/// }
/// </code></example>
[AttributeUsage(
AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |
AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event |
AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)]
public sealed class NotNullAttribute : Attribute { }
/// <summary>
/// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task
/// and Lazy classes to indicate that the value of a collection item, of the Task.Result property
/// or of the Lazy.Value property can never be null.
/// </summary>
[AttributeUsage(
AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |
AttributeTargets.Delegate | AttributeTargets.Field)]
public sealed class ItemNotNullAttribute : Attribute { }
/// <summary>
/// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task
/// and Lazy classes to indicate that the value of a collection item, of the Task.Result property
/// or of the Lazy.Value property can be null.
/// </summary>
[AttributeUsage(
AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |
AttributeTargets.Delegate | AttributeTargets.Field)]
public sealed class ItemCanBeNullAttribute : Attribute { }
/// <summary>
/// Indicates that the marked method builds string by format pattern and (optional) arguments.
/// Parameter, which contains format string, should be given in constructor. The format string
/// should be in <see cref="string.Format(IFormatProvider,string,object[])"/>-like form.
/// </summary>
/// <example><code>
/// [StringFormatMethod("message")]
/// void ShowError(string message, params object[] args) { /* do something */ }
///
/// void Foo() {
/// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string
/// }
/// </code></example>
[AttributeUsage(
AttributeTargets.Constructor | AttributeTargets.Method |
AttributeTargets.Property | AttributeTargets.Delegate)]
public sealed class StringFormatMethodAttribute : Attribute
{
/// <param name="formatParameterName">
/// Specifies which parameter of an annotated method should be treated as format-string
/// </param>
public StringFormatMethodAttribute([NotNull] string formatParameterName)
{
FormatParameterName = formatParameterName;
}
[NotNull] public string FormatParameterName { get; private set; }
}
/// <summary>
/// For a parameter that is expected to be one of the limited set of values.
/// Specify fields of which type should be used as values for this parameter.
/// </summary>
[AttributeUsage(
AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field,
AllowMultiple = true)]
public sealed class ValueProviderAttribute : Attribute
{
public ValueProviderAttribute([NotNull] string name)
{
Name = name;
}
[NotNull] public string Name { get; private set; }
}
/// <summary>
/// Indicates that the function argument should be string literal and match one
/// of the parameters of the caller function. For example, ReSharper annotates
/// the parameter of <see cref="System.ArgumentNullException"/>.
/// </summary>
/// <example><code>
/// void Foo(string param) {
/// if (param == null)
/// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol
/// }
/// </code></example>
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class InvokerParameterNameAttribute : Attribute { }
/// <summary>
/// Indicates that the method is contained in a type that implements
/// <c>System.ComponentModel.INotifyPropertyChanged</c> interface and this method
/// is used to notify that some property value changed.
/// </summary>
/// <remarks>
/// The method should be non-static and conform to one of the supported signatures:
/// <list>
/// <item><c>NotifyChanged(string)</c></item>
/// <item><c>NotifyChanged(params string[])</c></item>
/// <item><c>NotifyChanged{T}(Expression{Func{T}})</c></item>
/// <item><c>NotifyChanged{T,U}(Expression{Func{T,U}})</c></item>
/// <item><c>SetProperty{T}(ref T, T, string)</c></item>
/// </list>
/// </remarks>
/// <example><code>
/// public class Foo : INotifyPropertyChanged {
/// public event PropertyChangedEventHandler PropertyChanged;
///
/// [NotifyPropertyChangedInvocator]
/// protected virtual void NotifyChanged(string propertyName) { ... }
///
/// string _name;
///
/// public string Name {
/// get { return _name; }
/// set { _name = value; NotifyChanged("LastName"); /* Warning */ }
/// }
/// }
/// </code>
/// Examples of generated notifications:
/// <list>
/// <item><c>NotifyChanged("Property")</c></item>
/// <item><c>NotifyChanged(() =&gt; Property)</c></item>
/// <item><c>NotifyChanged((VM x) =&gt; x.Property)</c></item>
/// <item><c>SetProperty(ref myField, value, "Property")</c></item>
/// </list>
/// </example>
[AttributeUsage(AttributeTargets.Method)]
public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute
{
public NotifyPropertyChangedInvocatorAttribute() { }
public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName)
{
ParameterName = parameterName;
}
[CanBeNull] public string ParameterName { get; private set; }
}
/// <summary>
/// Describes dependency between method input and output.
/// </summary>
/// <syntax>
/// <p>Function Definition Table syntax:</p>
/// <list>
/// <item>FDT ::= FDTRow [;FDTRow]*</item>
/// <item>FDTRow ::= Input =&gt; Output | Output &lt;= Input</item>
/// <item>Input ::= ParameterName: Value [, Input]*</item>
/// <item>Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value}</item>
/// <item>Value ::= true | false | null | notnull | canbenull</item>
/// </list>
/// If method has single input parameter, it's name could be omitted.<br/>
/// Using <c>halt</c> (or <c>void</c>/<c>nothing</c>, which is the same) for method output
/// means that the methos doesn't return normally (throws or terminates the process).<br/>
/// Value <c>canbenull</c> is only applicable for output parameters.<br/>
/// You can use multiple <c>[ContractAnnotation]</c> for each FDT row, or use single attribute
/// with rows separated by semicolon. There is no notion of order rows, all rows are checked
/// for applicability and applied per each program state tracked by R# analysis.<br/>
/// </syntax>
/// <examples><list>
/// <item><code>
/// [ContractAnnotation("=&gt; halt")]
/// public void TerminationMethod()
/// </code></item>
/// <item><code>
/// [ContractAnnotation("halt &lt;= condition: false")]
/// public void Assert(bool condition, string text) // regular assertion method
/// </code></item>
/// <item><code>
/// [ContractAnnotation("s:null =&gt; true")]
/// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty()
/// </code></item>
/// <item><code>
/// // A method that returns null if the parameter is null,
/// // and not null if the parameter is not null
/// [ContractAnnotation("null =&gt; null; notnull =&gt; notnull")]
/// public object Transform(object data)
/// </code></item>
/// <item><code>
/// [ContractAnnotation("=&gt; true, result: notnull; =&gt; false, result: null")]
/// public bool TryParse(string s, out Person result)
/// </code></item>
/// </list></examples>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public sealed class ContractAnnotationAttribute : Attribute
{
public ContractAnnotationAttribute([NotNull] string contract)
: this(contract, false) { }
public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates)
{
Contract = contract;
ForceFullStates = forceFullStates;
}
[NotNull] public string Contract { get; private set; }
public bool ForceFullStates { get; private set; }
}
/// <summary>
/// Indicates that marked element should be localized or not.
/// </summary>
/// <example><code>
/// [LocalizationRequiredAttribute(true)]
/// class Foo {
/// string str = "my string"; // Warning: Localizable string
/// }
/// </code></example>
[AttributeUsage(AttributeTargets.All)]
public sealed class LocalizationRequiredAttribute : Attribute
{
public LocalizationRequiredAttribute() : this(true) { }
public LocalizationRequiredAttribute(bool required)
{
Required = required;
}
public bool Required { get; private set; }
}
/// <summary>
/// Indicates that the value of the marked type (or its derivatives)
/// cannot be compared using '==' or '!=' operators and <c>Equals()</c>
/// should be used instead. However, using '==' or '!=' for comparison
/// with <c>null</c> is always permitted.
/// </summary>
/// <example><code>
/// [CannotApplyEqualityOperator]
/// class NoEquality { }
///
/// class UsesNoEquality {
/// void Test() {
/// var ca1 = new NoEquality();
/// var ca2 = new NoEquality();
/// if (ca1 != null) { // OK
/// bool condition = ca1 == ca2; // Warning
/// }
/// }
/// }
/// </code></example>
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)]
public sealed class CannotApplyEqualityOperatorAttribute : Attribute { }
/// <summary>
/// When applied to a target attribute, specifies a requirement for any type marked
/// with the target attribute to implement or inherit specific type or types.
/// </summary>
/// <example><code>
/// [BaseTypeRequired(typeof(IComponent)] // Specify requirement
/// class ComponentAttribute : Attribute { }
///
/// [Component] // ComponentAttribute requires implementing IComponent interface
/// class MyComponent : IComponent { }
/// </code></example>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
[BaseTypeRequired(typeof(Attribute))]
public sealed class BaseTypeRequiredAttribute : Attribute
{
public BaseTypeRequiredAttribute([NotNull] Type baseType)
{
BaseType = baseType;
}
[NotNull] public Type BaseType { get; private set; }
}
/// <summary>
/// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library),
/// so this symbol will not be marked as unused (as well as by other usage inspections).
/// </summary>
[AttributeUsage(AttributeTargets.All)]
public sealed class UsedImplicitlyAttribute : Attribute
{
public UsedImplicitlyAttribute()
: this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { }
public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags)
: this(useKindFlags, ImplicitUseTargetFlags.Default) { }
public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags)
: this(ImplicitUseKindFlags.Default, targetFlags) { }
public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags)
{
UseKindFlags = useKindFlags;
TargetFlags = targetFlags;
}
public ImplicitUseKindFlags UseKindFlags { get; private set; }
public ImplicitUseTargetFlags TargetFlags { get; private set; }
}
/// <summary>
/// Should be used on attributes and causes ReSharper to not mark symbols marked with such attributes
/// as unused (as well as by other usage inspections)
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter)]
public sealed class MeansImplicitUseAttribute : Attribute
{
public MeansImplicitUseAttribute()
: this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { }
public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags)
: this(useKindFlags, ImplicitUseTargetFlags.Default) { }
public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags)
: this(ImplicitUseKindFlags.Default, targetFlags) { }
public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags)
{
UseKindFlags = useKindFlags;
TargetFlags = targetFlags;
}
[UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; private set; }
[UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; private set; }
}
[Flags]
public enum ImplicitUseKindFlags
{
Default = Access | Assign | InstantiatedWithFixedConstructorSignature,
/// <summary>Only entity marked with attribute considered used.</summary>
Access = 1,
/// <summary>Indicates implicit assignment to a member.</summary>
Assign = 2,
/// <summary>
/// Indicates implicit instantiation of a type with fixed constructor signature.
/// That means any unused constructor parameters won't be reported as such.
/// </summary>
InstantiatedWithFixedConstructorSignature = 4,
/// <summary>Indicates implicit instantiation of a type.</summary>
InstantiatedNoFixedConstructorSignature = 8,
}
/// <summary>
/// Specify what is considered used implicitly when marked
/// with <see cref="MeansImplicitUseAttribute"/> or <see cref="UsedImplicitlyAttribute"/>.
/// </summary>
[Flags]
public enum ImplicitUseTargetFlags
{
Default = Itself,
Itself = 1,
/// <summary>Members of entity marked with attribute are considered used.</summary>
Members = 2,
/// <summary>Entity marked with attribute and all its members considered used.</summary>
WithMembers = Itself | Members
}
/// <summary>
/// This attribute is intended to mark publicly available API
/// which should not be removed and so is treated as used.
/// </summary>
[MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)]
public sealed class PublicAPIAttribute : Attribute
{
public PublicAPIAttribute() { }
public PublicAPIAttribute([NotNull] string comment)
{
Comment = comment;
}
[CanBeNull] public string Comment { get; private set; }
}
/// <summary>
/// Tells code analysis engine if the parameter is completely handled when the invoked method is on stack.
/// If the parameter is a delegate, indicates that delegate is executed while the method is executed.
/// If the parameter is an enumerable, indicates that it is enumerated while the method is executed.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class InstantHandleAttribute : Attribute { }
/// <summary>
/// Indicates that a method does not make any observable state changes.
/// The same as <c>System.Diagnostics.Contracts.PureAttribute</c>.
/// </summary>
/// <example><code>
/// [Pure] int Multiply(int x, int y) => x * y;
///
/// void M() {
/// Multiply(123, 42); // Waring: Return value of pure method is not used
/// }
/// </code></example>
[AttributeUsage(AttributeTargets.Method)]
public sealed class PureAttribute : Attribute { }
/// <summary>
/// Indicates that the return value of method invocation must be used.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class MustUseReturnValueAttribute : Attribute
{
public MustUseReturnValueAttribute() { }
public MustUseReturnValueAttribute([NotNull] string justification)
{
Justification = justification;
}
[CanBeNull] public string Justification { get; private set; }
}
/// <summary>
/// Indicates the type member or parameter of some type, that should be used instead of all other ways
/// to get the value that type. This annotation is useful when you have some "context" value evaluated
/// and stored somewhere, meaning that all other ways to get this value must be consolidated with existing one.
/// </summary>
/// <example><code>
/// class Foo {
/// [ProvidesContext] IBarService _barService = ...;
///
/// void ProcessNode(INode node) {
/// DoSomething(node, node.GetGlobalServices().Bar);
/// // ^ Warning: use value of '_barService' field
/// }
/// }
/// </code></example>
[AttributeUsage(
AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Method |
AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.GenericParameter)]
public sealed class ProvidesContextAttribute : Attribute { }
/// <summary>
/// Indicates that a parameter is a path to a file or a folder within a web project.
/// Path can be relative or absolute, starting from web root (~).
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class PathReferenceAttribute : Attribute
{
public PathReferenceAttribute() { }
public PathReferenceAttribute([NotNull, PathReference] string basePath)
{
BasePath = basePath;
}
[CanBeNull] public string BasePath { get; private set; }
}
/// <summary>
/// An extension method marked with this attribute is processed by ReSharper code completion
/// as a 'Source Template'. When extension method is completed over some expression, it's source code
/// is automatically expanded like a template at call site.
/// </summary>
/// <remarks>
/// Template method body can contain valid source code and/or special comments starting with '$'.
/// Text inside these comments is added as source code when the template is applied. Template parameters
/// can be used either as additional method parameters or as identifiers wrapped in two '$' signs.
/// Use the <see cref="MacroAttribute"/> attribute to specify macros for parameters.
/// </remarks>
/// <example>
/// In this example, the 'forEach' method is a source template available over all values
/// of enumerable types, producing ordinary C# 'foreach' statement and placing caret inside block:
/// <code>
/// [SourceTemplate]
/// public static void forEach&lt;T&gt;(this IEnumerable&lt;T&gt; xs) {
/// foreach (var x in xs) {
/// //$ $END$
/// }
/// }
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Method)]
public sealed class SourceTemplateAttribute : Attribute { }
/// <summary>
/// Allows specifying a macro for a parameter of a <see cref="SourceTemplateAttribute">source template</see>.
/// </summary>
/// <remarks>
/// You can apply the attribute on the whole method or on any of its additional parameters. The macro expression
/// is defined in the <see cref="MacroAttribute.Expression"/> property. When applied on a method, the target
/// template parameter is defined in the <see cref="MacroAttribute.Target"/> property. To apply the macro silently
/// for the parameter, set the <see cref="MacroAttribute.Editable"/> property value = -1.
/// </remarks>
/// <example>
/// Applying the attribute on a source template method:
/// <code>
/// [SourceTemplate, Macro(Target = "item", Expression = "suggestVariableName()")]
/// public static void forEach&lt;T&gt;(this IEnumerable&lt;T&gt; collection) {
/// foreach (var item in collection) {
/// //$ $END$
/// }
/// }
/// </code>
/// Applying the attribute on a template method parameter:
/// <code>
/// [SourceTemplate]
/// public static void something(this Entity x, [Macro(Expression = "guid()", Editable = -1)] string newguid) {
/// /*$ var $x$Id = "$newguid$" + x.ToString();
/// x.DoSomething($x$Id); */
/// }
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = true)]
public sealed class MacroAttribute : Attribute
{
/// <summary>
/// Allows specifying a macro that will be executed for a <see cref="SourceTemplateAttribute">source template</see>
/// parameter when the template is expanded.
/// </summary>
[CanBeNull] public string Expression { get; set; }
/// <summary>
/// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed.
/// </summary>
/// <remarks>
/// If the target parameter is used several times in the template, only one occurrence becomes editable;
/// other occurrences are changed synchronously. To specify the zero-based index of the editable occurrence,
/// use values >= 0. To make the parameter non-editable when the template is expanded, use -1.
/// </remarks>>
public int Editable { get; set; }
/// <summary>
/// Identifies the target parameter of a <see cref="SourceTemplateAttribute">source template</see> if the
/// <see cref="MacroAttribute"/> is applied on a template method.
/// </summary>
[CanBeNull] public string Target { get; set; }
}
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
public sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute
{
public AspMvcAreaMasterLocationFormatAttribute([NotNull] string format)
{
Format = format;
}
[NotNull] public string Format { get; private set; }
}
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
public sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute
{
public AspMvcAreaPartialViewLocationFormatAttribute([NotNull] string format)
{
Format = format;
}
[NotNull] public string Format { get; private set; }
}
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
public sealed class AspMvcAreaViewLocationFormatAttribute : Attribute
{
public AspMvcAreaViewLocationFormatAttribute([NotNull] string format)
{
Format = format;
}
[NotNull] public string Format { get; private set; }
}
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
public sealed class AspMvcMasterLocationFormatAttribute : Attribute
{
public AspMvcMasterLocationFormatAttribute([NotNull] string format)
{
Format = format;
}
[NotNull] public string Format { get; private set; }
}
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
public sealed class AspMvcPartialViewLocationFormatAttribute : Attribute
{
public AspMvcPartialViewLocationFormatAttribute([NotNull] string format)
{
Format = format;
}
[NotNull] public string Format { get; private set; }
}
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
public sealed class AspMvcViewLocationFormatAttribute : Attribute
{
public AspMvcViewLocationFormatAttribute([NotNull] string format)
{
Format = format;
}
[NotNull] public string Format { get; private set; }
}
/// <summary>
/// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter
/// is an MVC action. If applied to a method, the MVC action name is calculated
/// implicitly from the context. Use this attribute for custom wrappers similar to
/// <c>System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String)</c>.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)]
public sealed class AspMvcActionAttribute : Attribute
{
public AspMvcActionAttribute() { }
public AspMvcActionAttribute([NotNull] string anonymousProperty)
{
AnonymousProperty = anonymousProperty;
}
[CanBeNull] public string AnonymousProperty { get; private set; }
}
/// <summary>
/// ASP.NET MVC attribute. Indicates that a parameter is an MVC area.
/// Use this attribute for custom wrappers similar to
/// <c>System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String)</c>.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class AspMvcAreaAttribute : Attribute
{
public AspMvcAreaAttribute() { }
public AspMvcAreaAttribute([NotNull] string anonymousProperty)
{
AnonymousProperty = anonymousProperty;
}
[CanBeNull] public string AnonymousProperty { get; private set; }
}
/// <summary>
/// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is
/// an MVC controller. If applied to a method, the MVC controller name is calculated
/// implicitly from the context. Use this attribute for custom wrappers similar to
/// <c>System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String)</c>.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)]
public sealed class AspMvcControllerAttribute : Attribute
{
public AspMvcControllerAttribute() { }
public AspMvcControllerAttribute([NotNull] string anonymousProperty)
{
AnonymousProperty = anonymousProperty;
}
[CanBeNull] public string AnonymousProperty { get; private set; }
}
/// <summary>
/// ASP.NET MVC attribute. Indicates that a parameter is an MVC Master. Use this attribute
/// for custom wrappers similar to <c>System.Web.Mvc.Controller.View(String, String)</c>.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class AspMvcMasterAttribute : Attribute { }
/// <summary>
/// ASP.NET MVC attribute. Indicates that a parameter is an MVC model type. Use this attribute
/// for custom wrappers similar to <c>System.Web.Mvc.Controller.View(String, Object)</c>.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class AspMvcModelTypeAttribute : Attribute { }
/// <summary>
/// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is an MVC
/// partial view. If applied to a method, the MVC partial view name is calculated implicitly
/// from the context. Use this attribute for custom wrappers similar to
/// <c>System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String)</c>.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)]
public sealed class AspMvcPartialViewAttribute : Attribute { }
/// <summary>
/// ASP.NET MVC attribute. Allows disabling inspections for MVC views within a class or a method.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class AspMvcSuppressViewErrorAttribute : Attribute { }
/// <summary>
/// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template.
/// Use this attribute for custom wrappers similar to
/// <c>System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String)</c>.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class AspMvcDisplayTemplateAttribute : Attribute { }
/// <summary>
/// ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template.
/// Use this attribute for custom wrappers similar to
/// <c>System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String)</c>.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class AspMvcEditorTemplateAttribute : Attribute { }
/// <summary>
/// ASP.NET MVC attribute. Indicates that a parameter is an MVC template.
/// Use this attribute for custom wrappers similar to
/// <c>System.ComponentModel.DataAnnotations.UIHintAttribute(System.String)</c>.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class AspMvcTemplateAttribute : Attribute { }
/// <summary>
/// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter
/// is an MVC view component. If applied to a method, the MVC view name is calculated implicitly
/// from the context. Use this attribute for custom wrappers similar to
/// <c>System.Web.Mvc.Controller.View(Object)</c>.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)]
public sealed class AspMvcViewAttribute : Attribute { }
/// <summary>
/// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter
/// is an MVC view component name.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class AspMvcViewComponentAttribute : Attribute { }
/// <summary>
/// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter
/// is an MVC view component view. If applied to a method, the MVC view component view name is default.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)]
public sealed class AspMvcViewComponentViewAttribute : Attribute { }
/// <summary>
/// ASP.NET MVC attribute. When applied to a parameter of an attribute,
/// indicates that this parameter is an MVC action name.
/// </summary>
/// <example><code>
/// [ActionName("Foo")]
/// public ActionResult Login(string returnUrl) {
/// ViewBag.ReturnUrl = Url.Action("Foo"); // OK
/// return RedirectToAction("Bar"); // Error: Cannot resolve action
/// }
/// </code></example>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)]
public sealed class AspMvcActionSelectorAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)]
public sealed class HtmlElementAttributesAttribute : Attribute
{
public HtmlElementAttributesAttribute() { }
public HtmlElementAttributesAttribute([NotNull] string name)
{
Name = name;
}
[CanBeNull] public string Name { get; private set; }
}
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)]
public sealed class HtmlAttributeValueAttribute : Attribute
{
public HtmlAttributeValueAttribute([NotNull] string name)
{
Name = name;
}
[NotNull] public string Name { get; private set; }
}
/// <summary>
/// Razor attribute. Indicates that a parameter or a method is a Razor section.
/// Use this attribute for custom wrappers similar to
/// <c>System.Web.WebPages.WebPageBase.RenderSection(String)</c>.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)]
public sealed class RazorSectionAttribute : Attribute { }
/// <summary>
/// Indicates how method, constructor invocation or property access
/// over collection type affects content of the collection.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property)]
public sealed class CollectionAccessAttribute : Attribute
{
public CollectionAccessAttribute(CollectionAccessType collectionAccessType)
{
CollectionAccessType = collectionAccessType;
}
public CollectionAccessType CollectionAccessType { get; private set; }
}
[Flags]
public enum CollectionAccessType
{
/// <summary>Method does not use or modify content of the collection.</summary>
None = 0,
/// <summary>Method only reads content of the collection but does not modify it.</summary>
Read = 1,
/// <summary>Method can change content of the collection but does not add new elements.</summary>
ModifyExistingContent = 2,
/// <summary>Method can add new elements to the collection.</summary>
UpdatedContent = ModifyExistingContent | 4
}
/// <summary>
/// Indicates that the marked method is assertion method, i.e. it halts control flow if
/// one of the conditions is satisfied. To set the condition, mark one of the parameters with
/// <see cref="AssertionConditionAttribute"/> attribute.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class AssertionMethodAttribute : Attribute { }
/// <summary>
/// Indicates the condition parameter of the assertion method. The method itself should be
/// marked by <see cref="AssertionMethodAttribute"/> attribute. The mandatory argument of
/// the attribute is the assertion type.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class AssertionConditionAttribute : Attribute
{
public AssertionConditionAttribute(AssertionConditionType conditionType)
{
ConditionType = conditionType;
}
public AssertionConditionType ConditionType { get; private set; }
}
/// <summary>
/// Specifies assertion type. If the assertion method argument satisfies the condition,
/// then the execution continues. Otherwise, execution is assumed to be halted.
/// </summary>
public enum AssertionConditionType
{
/// <summary>Marked parameter should be evaluated to true.</summary>
IS_TRUE = 0,
/// <summary>Marked parameter should be evaluated to false.</summary>
IS_FALSE = 1,
/// <summary>Marked parameter should be evaluated to null value.</summary>
IS_NULL = 2,
/// <summary>Marked parameter should be evaluated to not null value.</summary>
IS_NOT_NULL = 3,
}
/// <summary>
/// Indicates that the marked method unconditionally terminates control flow execution.
/// For example, it could unconditionally throw exception.
/// </summary>
[Obsolete("Use [ContractAnnotation('=> halt')] instead")]
[AttributeUsage(AttributeTargets.Method)]
public sealed class TerminatesProgramAttribute : Attribute { }
/// <summary>
/// Indicates that method is pure LINQ method, with postponed enumeration (like Enumerable.Select,
/// .Where). This annotation allows inference of [InstantHandle] annotation for parameters
/// of delegate type by analyzing LINQ method chains.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class LinqTunnelAttribute : Attribute { }
/// <summary>
/// Indicates that IEnumerable, passed as parameter, is not enumerated.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class NoEnumerationAttribute : Attribute { }
/// <summary>
/// Indicates that parameter is regular expression pattern.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class RegexPatternAttribute : Attribute { }
/// <summary>
/// Prevents the Member Reordering feature from tossing members of the marked class.
/// </summary>
/// <remarks>
/// The attribute must be mentioned in your member reordering patterns
/// </remarks>
[AttributeUsage(
AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum)]
public sealed class NoReorderAttribute : Attribute { }
/// <summary>
/// XAML attribute. Indicates the type that has <c>ItemsSource</c> property and should be treated
/// as <c>ItemsControl</c>-derived type, to enable inner items <c>DataContext</c> type resolve.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class XamlItemsControlAttribute : Attribute { }
/// <summary>
/// XAML attribute. Indicates the property of some <c>BindingBase</c>-derived type, that
/// is used to bind some item of <c>ItemsControl</c>-derived type. This annotation will
/// enable the <c>DataContext</c> type resolve for XAML bindings for such properties.
/// </summary>
/// <remarks>
/// Property should have the tree ancestor of the <c>ItemsControl</c> type or
/// marked with the <see cref="XamlItemsControlAttribute"/> attribute.
/// </remarks>
[AttributeUsage(AttributeTargets.Property)]
public sealed class XamlItemBindingOfItemsControlAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class AspChildControlTypeAttribute : Attribute
{
public AspChildControlTypeAttribute([NotNull] string tagName, [NotNull] Type controlType)
{
TagName = tagName;
ControlType = controlType;
}
[NotNull] public string TagName { get; private set; }
[NotNull] public Type ControlType { get; private set; }
}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]
public sealed class AspDataFieldAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]
public sealed class AspDataFieldsAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Property)]
public sealed class AspMethodPropertyAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class AspRequiredAttributeAttribute : Attribute
{
public AspRequiredAttributeAttribute([NotNull] string attribute)
{
Attribute = attribute;
}
[NotNull] public string Attribute { get; private set; }
}
[AttributeUsage(AttributeTargets.Property)]
public sealed class AspTypePropertyAttribute : Attribute
{
public bool CreateConstructorReferences { get; private set; }
public AspTypePropertyAttribute(bool createConstructorReferences)
{
CreateConstructorReferences = createConstructorReferences;
}
}
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class RazorImportNamespaceAttribute : Attribute
{
public RazorImportNamespaceAttribute([NotNull] string name)
{
Name = name;
}
[NotNull] public string Name { get; private set; }
}
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class RazorInjectionAttribute : Attribute
{
public RazorInjectionAttribute([NotNull] string type, [NotNull] string fieldName)
{
Type = type;
FieldName = fieldName;
}
[NotNull] public string Type { get; private set; }
[NotNull] public string FieldName { get; private set; }
}
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class RazorDirectiveAttribute : Attribute
{
public RazorDirectiveAttribute([NotNull] string directive)
{
Directive = directive;
}
[NotNull] public string Directive { get; private set; }
}
[AttributeUsage(AttributeTargets.Method)]
public sealed class RazorHelperCommonAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Property)]
public sealed class RazorLayoutAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Method)]
public sealed class RazorWriteLiteralMethodAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Method)]
public sealed class RazorWriteMethodAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class RazorWriteMethodParameterAttribute : Attribute { }
}
\ No newline at end of file
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Demo WPF")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Demo WPF")]
[assembly: AssemblyCopyright("Copyright ©2017 Titanium")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
//In order to begin building localizable applications, set
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
//inside a <PropertyGroup>. For example, if you are using US english
//in your source files, set the <UICulture> to en-US. Then uncomment
//the NeutralResourceLanguage attribute below. Update the "en-US" in
//the line below to match the UICulture setting in the project file.
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Titanium.Web.Proxy.Examples.Wpf.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Titanium.Web.Proxy.Examples.Wpf.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
\ No newline at end of file
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Titanium.Web.Proxy.Examples.Wpf.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.1.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Examples.Wpf.Annotations;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Examples.Wpf
{
public class SessionListItem : INotifyPropertyChanged
{
private string statusCode;
private string protocol;
private string host;
private string url;
private long bodySize;
private string process;
private long receivedDataCount;
private long sentDataCount;
public int Number { get; set; }
public SessionEventArgs SessionArgs { get; set; }
public string StatusCode
{
get { return statusCode; }
set { SetField(ref statusCode, value);}
}
public string Protocol
{
get { return protocol; }
set { SetField(ref protocol, value); }
}
public string Host
{
get { return host; }
set { SetField(ref host, value); }
}
public string Url
{
get { return url; }
set { SetField(ref url, value); }
}
public long BodySize
{
get { return bodySize; }
set { SetField(ref bodySize, value); }
}
public string Process
{
get { return process; }
set { SetField(ref process, value); }
}
public long ReceivedDataCount
{
get { return receivedDataCount; }
set { SetField(ref receivedDataCount, value); }
}
public long SentDataCount
{
get { return sentDataCount; }
set { SetField(ref sentDataCount, value); }
}
public byte[] RequestBody { get; set; }
public byte[] ResponseBody { get; set; }
public Request Request { get; set; }
public Response Response { get; set; }
public HeaderCollection RequestHeaders { get; set; }
public HeaderCollection ResponseHeaders { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void SetField<T>(ref T field, T value,[CallerMemberName] string propertyName = null)
{
field = value;
OnPropertyChanged(propertyName);
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void Update()
{
var request = SessionArgs.WebSession.Request;
var response = SessionArgs.WebSession.Response;
int statusCode = response?.ResponseStatusCode ?? 0;
StatusCode = statusCode == 0 ? "-" : statusCode.ToString();
Protocol = request.RequestUri.Scheme;
if (SessionArgs is TunnelConnectSessionEventArgs)
{
Host = "Tunnel to";
Url = request.RequestUri.Host + ":" + request.RequestUri.Port;
}
else
{
Host = request.RequestUri.Host;
Url = request.RequestUri.AbsolutePath;
}
BodySize = response?.ContentLength ?? -1;
Process = GetProcessDescription(SessionArgs.WebSession.ProcessId.Value);
}
private string GetProcessDescription(int processId)
{
try
{
var process = System.Diagnostics.Process.GetProcessById(processId);
return process.ProcessName + ":" + processId;
}
catch (Exception)
{
return string.Empty;
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{4406CE17-9A39-4F28-8363-6169A4F799C1}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>Titanium.Web.Proxy.Examples.Wpf</RootNamespace>
<AssemblyName>Titanium.Web.Proxy.Examples.Wpf</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="Properties\Annotations.cs" />
<Compile Include="SessionListItem.cs" />
<Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<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>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
\ No newline at end of file
...@@ -2,10 +2,12 @@ Titanium ...@@ -2,10 +2,12 @@ Titanium
======== ========
A light weight HTTP(S) proxy server written in C# A light weight HTTP(S) proxy server written in C#
![Build Status](https://ci.appveyor.com/api/projects/status/rvlxv8xgj0m7lkr4?svg=true) <a href="https://ci.appveyor.com/project/justcoding121/titanium-web-proxy">![Build Status](https://ci.appveyor.com/api/projects/status/rvlxv8xgj0m7lkr4?svg=true)</a>
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))
![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/develop/Examples/Titanium.Web.Proxy.Examples.Basic/Capture.PNG)
Features Features
...@@ -23,7 +25,7 @@ Features ...@@ -23,7 +25,7 @@ Features
Usage Usage
===== =====
Refer the HTTP Proxy Server library in your project, look up Test project to learn usage. ([Wiki & Contribution guidelines](https://github.com/justcoding121/Titanium-Web-Proxy/wiki)) Refer the HTTP Proxy Server library in your project, look up Test project to learn usage.
Install by nuget: Install by nuget:
...@@ -71,17 +73,16 @@ var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true) ...@@ -71,17 +73,16 @@ var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
proxyServer.AddEndPoint(explicitEndPoint); proxyServer.AddEndPoint(explicitEndPoint);
proxyServer.Start(); proxyServer.Start();
//Warning! Transparent endpoint is not tested end to end
//Transparent endpoint is useful for reverse proxy (client is not aware of the existence of proxy) //Transparent endpoint is useful for reverse proxy (client is not aware of the existence of proxy)
//A transparent endpoint usually requires a network router port forwarding HTTP(S) packets to this endpoint //A transparent endpoint usually requires a network router port forwarding HTTP(S) packets or DNS
//Currently do not support Server Name Indication (It is not currently supported by SslStream class) //to send data to this endPoint
//That means that the transparent endpoint will always provide the same Generic Certificate to all HTTPS requests
//In this example only google.com will work for HTTPS requests
//Other sites will receive a certificate mismatch warning on browser
var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true) var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true)
{ {
GenericCertificateName = "google.com" //Generic Certificate hostname to use
//when SNI is disabled by client
GenericCertificateName = "google.com"
}; };
proxyServer.AddEndPoint(transparentEndPoint); proxyServer.AddEndPoint(transparentEndPoint);
//proxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 }; //proxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
......
...@@ -9,7 +9,7 @@ using System.Runtime.InteropServices; ...@@ -9,7 +9,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")] [assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Titanium.Web.Proxy.IntegrationTests")] [assembly: AssemblyProduct("Titanium.Web.Proxy.IntegrationTests")]
[assembly: AssemblyCopyright("Copyright © 2017")] [assembly: AssemblyCopyright("Copyright © Titanium 2015-2017")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
......
...@@ -13,12 +13,13 @@ namespace Titanium.Web.Proxy.IntegrationTests ...@@ -13,12 +13,13 @@ namespace Titanium.Web.Proxy.IntegrationTests
[TestClass] [TestClass]
public class SslTests public class SslTests
{ {
[TestMethod] //[TestMethod]
//disable this test until CI is prepared to handle
public void TestSsl() public void TestSsl()
{ {
//expand this to stress test to find //expand this to stress test to find
//why in long run proxy becomes unresponsive as per issue #184 //why in long run proxy becomes unresponsive as per issue #184
var testUrl = "https://google.com"; string testUrl = "https://google.com";
int proxyPort = 8086; int proxyPort = 8086;
var proxy = new ProxyTestController(); var proxy = new ProxyTestController();
proxy.StartProxy(proxyPort); proxy.StartProxy(proxyPort);
......
...@@ -25,7 +25,7 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -25,7 +25,7 @@ namespace Titanium.Web.Proxy.UnitTests
for (int i = 0; i < 1000; i++) for (int i = 0; i < 1000; i++)
{ {
foreach (var host in hostNames) foreach (string host in hostNames)
{ {
tasks.Add(Task.Run(async () => tasks.Add(Task.Run(async () =>
{ {
......
...@@ -9,7 +9,7 @@ using System.Runtime.InteropServices; ...@@ -9,7 +9,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")] [assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Titanium.Web.Proxy.UnitTests")] [assembly: AssemblyProduct("Titanium.Web.Proxy.UnitTests")]
[assembly: AssemblyCopyright("Copyright © 2016")] [assembly: AssemblyCopyright("Copyright © Titanium 2015-2017")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
......
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Net; using System.Net;
using System.Text;
using System.Threading.Tasks;
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 Microsoft.VisualStudio.TestTools.UnitTesting; using System;
using System; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Titanium.Web.Proxy.Network.WinAuth; using Titanium.Web.Proxy.Network.WinAuth;
namespace Titanium.Web.Proxy.UnitTests namespace Titanium.Web.Proxy.UnitTests
...@@ -10,7 +10,7 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -10,7 +10,7 @@ namespace Titanium.Web.Proxy.UnitTests
[TestMethod] [TestMethod]
public void Test_Acquire_Client_Token() public void Test_Acquire_Client_Token()
{ {
var token = WinAuthHandler.GetInitialAuthToken("mylocalserver.com", "NTLM", Guid.NewGuid()); string token = WinAuthHandler.GetInitialAuthToken("mylocalserver.com", "NTLM", Guid.NewGuid());
Assert.IsTrue(token.Length > 1); Assert.IsTrue(token.Length > 1);
} }
} }
......
...@@ -35,6 +35,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.UnitTest ...@@ -35,6 +35,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.UnitTest
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.IntegrationTests", "Tests\Titanium.Web.Proxy.IntegrationTests\Titanium.Web.Proxy.IntegrationTests.csproj", "{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.IntegrationTests", "Tests\Titanium.Web.Proxy.IntegrationTests\Titanium.Web.Proxy.IntegrationTests.csproj", "{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.Examples.Wpf", "Examples\Titanium.Web.Proxy.Examples.Wpf\Titanium.Web.Proxy.Examples.Wpf.csproj", "{4406CE17-9A39-4F28-8363-6169A4F799C1}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
...@@ -57,6 +59,10 @@ Global ...@@ -57,6 +59,10 @@ Global
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Debug|Any CPU.Build.0 = Debug|Any CPU {32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Release|Any CPU.ActiveCfg = Release|Any CPU {32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Release|Any CPU.Build.0 = Release|Any CPU {32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Release|Any CPU.Build.0 = Release|Any CPU
{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}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4406CE17-9A39-4F28-8363-6169A4F799C1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
...@@ -65,6 +71,7 @@ Global ...@@ -65,6 +71,7 @@ Global
{F3B7E553-1904-4E80-BDC7-212342B5C952} = {B6DBABDC-C985-4872-9C38-B4E5079CBC4B} {F3B7E553-1904-4E80-BDC7-212342B5C952} = {B6DBABDC-C985-4872-9C38-B4E5079CBC4B}
{B517E3D0-D03B-436F-AB03-34BA0D5321AF} = {BC1E0789-D348-49CF-8B67-5E99D50EDF64} {B517E3D0-D03B-436F-AB03-34BA0D5321AF} = {BC1E0789-D348-49CF-8B67-5E99D50EDF64}
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16} = {BC1E0789-D348-49CF-8B67-5E99D50EDF64} {32231301-B0FB-4F9E-98DF-B3E8A88F4C16} = {BC1E0789-D348-49CF-8B67-5E99D50EDF64}
{4406CE17-9A39-4F28-8363-6169A4F799C1} = {B6DBABDC-C985-4872-9C38-B4E5079CBC4B}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
EnterpriseLibraryConfigurationToolBinariesPath = .1.505.2\lib\NET35 EnterpriseLibraryConfigurationToolBinariesPath = .1.505.2\lib\NET35
......
<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/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/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: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">240</s:Int64> <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">160</s:Int64>
<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/Naming/CSharpNaming/Abbreviations/=BC/@EntryIndexedValue">BC</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/=CN/@EntryIndexedValue">CN</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CN/@EntryIndexedValue">CN</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DN/@EntryIndexedValue">DN</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DN/@EntryIndexedValue">DN</s:String>
......
...@@ -16,11 +16,7 @@ namespace Titanium.Web.Proxy ...@@ -16,11 +16,7 @@ namespace Titanium.Web.Proxy
/// <param name="chain"></param> /// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param> /// <param name="sslPolicyErrors"></param>
/// <returns></returns> /// <returns></returns>
internal bool ValidateServerCertificate( internal bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
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)
...@@ -56,22 +52,15 @@ namespace Titanium.Web.Proxy ...@@ -56,22 +52,15 @@ namespace Titanium.Web.Proxy
/// <param name="remoteCertificate"></param> /// <param name="remoteCertificate"></param>
/// <param name="acceptableIssuers"></param> /// <param name="acceptableIssuers"></param>
/// <returns></returns> /// <returns></returns>
internal X509Certificate SelectClientCertificate( internal X509Certificate SelectClientCertificate(object sender, string targetHost, X509CertificateCollection localCertificates,
object sender, X509Certificate remoteCertificate, string[] acceptableIssuers)
string targetHost,
X509CertificateCollection localCertificates,
X509Certificate remoteCertificate,
string[] acceptableIssuers)
{ {
X509Certificate clientCertificate = null; X509Certificate clientCertificate = null;
if (acceptableIssuers != null && if (acceptableIssuers != null && acceptableIssuers.Length > 0 && localCertificates != null && localCertificates.Count > 0)
acceptableIssuers.Length > 0 &&
localCertificates != null &&
localCertificates.Count > 0)
{ {
// Use the first certificate that is from an acceptable issuer. // Use the first certificate that is from an acceptable issuer.
foreach (X509Certificate certificate in localCertificates) foreach (var certificate in localCertificates)
{ {
string issuer = certificate.Issuer; string issuer = certificate.Issuer;
if (Array.IndexOf(acceptableIssuers, issuer) != -1) if (Array.IndexOf(acceptableIssuers, issuer) != -1)
...@@ -81,8 +70,7 @@ namespace Titanium.Web.Proxy ...@@ -81,8 +70,7 @@ namespace Titanium.Web.Proxy
} }
} }
if (localCertificates != null && if (localCertificates != null && localCertificates.Count > 0)
localCertificates.Count > 0)
{ {
clientCertificate = localCertificates[0]; clientCertificate = localCertificates[0];
} }
......
using System;
namespace Titanium.Web.Proxy.EventArguments
{
public class DataEventArgs : EventArgs
{
public byte[] Buffer { get; }
public int Offset { get; }
public int Count { get; }
public DataEventArgs(byte[] buffer, int offset, int count)
{
Buffer = buffer;
Offset = offset;
Count = count;
}
}
}
\ No newline at end of file
...@@ -7,6 +7,7 @@ using System.Threading.Tasks; ...@@ -7,6 +7,7 @@ using System.Threading.Tasks;
using Titanium.Web.Proxy.Decompression; using Titanium.Web.Proxy.Decompression;
using Titanium.Web.Proxy.Exceptions; using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Http.Responses; using Titanium.Web.Proxy.Http.Responses;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
...@@ -48,7 +49,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -48,7 +49,6 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
public Guid Id => WebSession.RequestId; public Guid Id => WebSession.RequestId;
/// <summary> /// <summary>
/// Should we send the request again /// Should we send the request again
/// </summary> /// </summary>
...@@ -57,10 +57,9 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -57,10 +57,9 @@ namespace Titanium.Web.Proxy.EventArguments
get { return reRequest; } get { return reRequest; }
set set
{ {
if (WebSession.Response.ResponseStatusCode == null) if (WebSession.Response.ResponseStatusCode == 0)
{ {
throw new Exception("Response status code is null. Cannot request again a request " throw new Exception("Response status code is empty. Cannot request again a request " + "which was never send to server.");
+ "which was never send to server.");
} }
reRequest = value; reRequest = value;
...@@ -70,7 +69,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -70,7 +69,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary> /// <summary>
/// Does this session uses SSL /// Does this session uses SSL
/// </summary> /// </summary>
public bool IsHttps => WebSession.Request.RequestUri.Scheme == Uri.UriSchemeHttps; public bool IsHttps => WebSession.Request.IsHttps;
/// <summary> /// <summary>
/// Client End Point. /// Client End Point.
...@@ -93,16 +92,38 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -93,16 +92,38 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
public ExternalProxy CustomUpStreamHttpsProxyUsed { get; set; } public ExternalProxy CustomUpStreamHttpsProxyUsed { get; set; }
public event EventHandler<DataEventArgs> DataSent;
public event EventHandler<DataEventArgs> DataReceived;
/// <summary> /// <summary>
/// Constructor to initialize the proxy /// Constructor to initialize the proxy
/// </summary> /// </summary>
internal SessionEventArgs(int bufferSize, Func<SessionEventArgs, Task> httpResponseHandler) internal SessionEventArgs(int bufferSize, ProxyEndPoint endPoint, Func<SessionEventArgs, Task> httpResponseHandler)
{ {
this.bufferSize = bufferSize; this.bufferSize = bufferSize;
this.httpResponseHandler = httpResponseHandler; this.httpResponseHandler = httpResponseHandler;
ProxyClient = new ProxyClient(); ProxyClient = new ProxyClient();
WebSession = new HttpWebClient(); WebSession = new HttpWebClient();
WebSession.ProcessId = new Lazy<int>(() =>
{
#if NET45
var remoteEndPoint = (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint;
//If client is localhost get the process id
if (NetworkHelper.IsLocalIpAddress(remoteEndPoint.Address))
{
return NetworkHelper.GetProcessIdFromPort(remoteEndPoint.Port, endPoint.IpV6Enabled);
}
//can't access process Id of remote request from remote machine
return -1;
#else
throw new PlatformNotSupportedException();
#endif
});
} }
/// <summary> /// <summary>
...@@ -113,8 +134,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -113,8 +134,7 @@ namespace Titanium.Web.Proxy.EventArguments
//GET request don't have a request body to read //GET request don't have a request body to read
if (!WebSession.Request.HasBody) if (!WebSession.Request.HasBody)
{ {
throw new BodyNotFoundException("Request don't have a body. " + throw new BodyNotFoundException("Request don't have a body. " + "Please verify that this request is a Http POST/PUT/PATCH and request " +
"Please verify that this request is a Http POST/PUT/PATCH and request " +
"content length is greater than zero before accessing the body."); "content length is greater than zero before accessing the body.");
} }
...@@ -135,21 +155,21 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -135,21 +155,21 @@ namespace Titanium.Web.Proxy.EventArguments
if (WebSession.Request.ContentLength > 0) if (WebSession.Request.ContentLength > 0)
{ {
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response //If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await ProxyClient.ClientStreamReader.CopyBytesToStream(requestBodyStream, await ProxyClient.ClientStreamReader.CopyBytesToStream(requestBodyStream, WebSession.Request.ContentLength);
WebSession.Request.ContentLength);
} }
else if (WebSession.Request.HttpVersion.Major == 1 && WebSession.Request.HttpVersion.Minor == 0) else if (WebSession.Request.HttpVersion.Major == 1 && WebSession.Request.HttpVersion.Minor == 0)
{ {
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(requestBodyStream, long.MaxValue); await WebSession.ServerConnection.StreamReader.CopyBytesToStream(requestBodyStream, long.MaxValue);
} }
} }
WebSession.Request.RequestBody = await GetDecompressedResponseBody(WebSession.Request.ContentEncoding,
requestBodyStream.ToArray()); WebSession.Request.RequestBody = await GetDecompressedResponseBody(WebSession.Request.ContentEncoding, requestBodyStream.ToArray());
} }
//Now set the flag to true //Now set the flag to true
//So that next time we can deliver body from cache //So that next time we can deliver body from cache
WebSession.Request.RequestBodyRead = true; WebSession.Request.RequestBodyRead = true;
OnDataSent(WebSession.Request.RequestBody, 0, WebSession.Request.RequestBody.Length);
} }
} }
...@@ -164,6 +184,15 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -164,6 +184,15 @@ namespace Titanium.Web.Proxy.EventArguments
WebSession.Response = new Response(); WebSession.Response = new Response();
} }
internal void OnDataSent(byte[] buffer, int offset, int count)
{
DataSent?.Invoke(this, new DataEventArgs(buffer, offset, count));
}
internal void OnDataReceived(byte[] buffer, int offset, int count)
{
DataReceived?.Invoke(this, new DataEventArgs(buffer, offset, count));
}
/// <summary> /// <summary>
/// Read response body as byte[] for current response /// Read response body as byte[] for current response
...@@ -172,6 +201,8 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -172,6 +201,8 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
//If not already read (not cached yet) //If not already read (not cached yet)
if (WebSession.Response.ResponseBody == null) if (WebSession.Response.ResponseBody == null)
{
if (WebSession.Response.HasBody)
{ {
using (var responseBodyStream = new MemoryStream()) using (var responseBodyStream = new MemoryStream())
{ {
...@@ -185,20 +216,26 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -185,20 +216,26 @@ namespace Titanium.Web.Proxy.EventArguments
if (WebSession.Response.ContentLength > 0) if (WebSession.Response.ContentLength > 0)
{ {
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response //If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream, await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream, WebSession.Response.ContentLength);
WebSession.Response.ContentLength);
} }
else if (WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0 || WebSession.Response.ContentLength == -1) else if (WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0 ||
WebSession.Response.ContentLength == -1)
{ {
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream, long.MaxValue); await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream, long.MaxValue);
} }
} }
WebSession.Response.ResponseBody = await GetDecompressedResponseBody(WebSession.Response.ContentEncoding, WebSession.Response.ResponseBody = await GetDecompressedResponseBody(WebSession.Response.ContentEncoding, responseBodyStream.ToArray());
responseBodyStream.ToArray());
} }
}
else
{
WebSession.Response.ResponseBody = new byte[0];
}
//set this to true for caching //set this to true for caching
WebSession.Response.ResponseBodyRead = true; WebSession.Response.ResponseBodyRead = true;
OnDataReceived(WebSession.Response.ResponseBody, 0, WebSession.Response.ResponseBody.Length);
} }
} }
...@@ -217,6 +254,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -217,6 +254,7 @@ namespace Titanium.Web.Proxy.EventArguments
await ReadRequestBody(); await ReadRequestBody();
} }
return WebSession.Request.RequestBody; return WebSession.Request.RequestBody;
} }
...@@ -235,8 +273,10 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -235,8 +273,10 @@ namespace Titanium.Web.Proxy.EventArguments
await ReadRequestBody(); await ReadRequestBody();
} }
//Use the encoding specified in request to decode the byte[] data to string //Use the encoding specified in request to decode the byte[] data to string
return WebSession.Request.RequestBodyString ?? (WebSession.Request.RequestBodyString = WebSession.Request.Encoding.GetString(WebSession.Request.RequestBody)); return WebSession.Request.RequestBodyString ?? (WebSession.Request.RequestBodyString =
WebSession.Request.Encoding.GetString(WebSession.Request.RequestBody));
} }
/// <summary> /// <summary>
...@@ -257,15 +297,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -257,15 +297,7 @@ namespace Titanium.Web.Proxy.EventArguments
} }
WebSession.Request.RequestBody = body; WebSession.Request.RequestBody = body;
WebSession.Request.ContentLength = WebSession.Request.IsChunked ? -1 : body.Length;
if (WebSession.Request.IsChunked == false)
{
WebSession.Request.ContentLength = body.Length;
}
else
{
WebSession.Request.ContentLength = -1;
}
} }
/// <summary> /// <summary>
...@@ -316,8 +348,8 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -316,8 +348,8 @@ namespace Titanium.Web.Proxy.EventArguments
await GetResponseBody(); await GetResponseBody();
return WebSession.Response.ResponseBodyString ?? return WebSession.Response.ResponseBodyString ?? (WebSession.Response.ResponseBodyString =
(WebSession.Response.ResponseBodyString = WebSession.Response.Encoding.GetString(WebSession.Response.ResponseBody)); WebSession.Response.Encoding.GetString(WebSession.Response.ResponseBody));
} }
/// <summary> /// <summary>
...@@ -380,17 +412,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -380,17 +412,6 @@ namespace Titanium.Web.Proxy.EventArguments
return await decompressor.Decompress(responseBodyStream, bufferSize); return await decompressor.Decompress(responseBodyStream, bufferSize);
} }
/// <summary>
/// Before request is made to server
/// Respond with the specified HTML string to client
/// and ignore the request
/// </summary>
/// <param name="html"></param>
public async Task Ok(string html)
{
await Ok(html, null);
}
/// <summary> /// <summary>
/// Before request is made to server /// Before request is made to server
/// Respond with the specified HTML string to client /// Respond with the specified HTML string to client
...@@ -400,30 +421,14 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -400,30 +421,14 @@ namespace Titanium.Web.Proxy.EventArguments
/// <param name="headers"></param> /// <param name="headers"></param>
public async Task Ok(string html, Dictionary<string, HttpHeader> headers) public async Task Ok(string html, Dictionary<string, HttpHeader> headers)
{ {
if (WebSession.Request.RequestLocked) var response = new OkResponse();
{ response.ResponseHeaders.AddHeaders(headers);
throw new Exception("You cannot call this function after request is made to server."); response.HttpVersion = WebSession.Request.HttpVersion;
} response.ResponseBody = response.Encoding.GetBytes(html ?? string.Empty);
if (html == null)
{
html = string.Empty;
}
var result = Encoding.Default.GetBytes(html);
await Ok(result, headers); await Respond(response);
}
/// <summary> WebSession.Request.CancelRequest = true;
/// Before request is made to server
/// Respond with the specified byte[] to client
/// and ignore the request
/// </summary>
/// <param name="result"></param>
public async Task Ok(byte[] result)
{
await Ok(result, null);
} }
/// <summary> /// <summary>
...@@ -433,34 +438,14 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -433,34 +438,14 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
/// <param name="result"></param> /// <param name="result"></param>
/// <param name="headers"></param> /// <param name="headers"></param>
public async Task Ok(byte[] result, Dictionary<string, HttpHeader> headers) public async Task Ok(byte[] result, Dictionary<string, HttpHeader> headers = null)
{ {
var response = new OkResponse(); var response = new OkResponse();
response.ResponseHeaders.AddHeaders(headers);
if (headers != null && headers.Count > 0)
{
response.ResponseHeaders = headers;
}
response.HttpVersion = WebSession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseBody = result; response.ResponseBody = result;
await Respond(response); await Respond(response);
WebSession.Request.CancelRequest = true;
}
/// <summary>
/// Before request is made to server 
/// Respond with the specified HTML string to client
/// and ignore the request 
/// </summary>
/// <param name="html"></param>
/// <param name="status"></param>
/// <returns></returns>
public async Task GenericResponse(string html, HttpStatusCode status)
{
await GenericResponse(html, null, status);
} }
/// <summary> /// <summary>
...@@ -470,24 +455,17 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -470,24 +455,17 @@ namespace Titanium.Web.Proxy.EventArguments
/// and ignore the request  /// and ignore the request 
/// </summary> /// </summary>
/// <param name="html"></param> /// <param name="html"></param>
/// <param name="headers"></param>
/// <param name="status"></param> /// <param name="status"></param>
/// <param name="headers"></param>
/// <returns></returns> /// <returns></returns>
public async Task GenericResponse(string html, Dictionary<string, HttpHeader> headers, HttpStatusCode status) public async Task GenericResponse(string html, HttpStatusCode status, Dictionary<string, HttpHeader> headers = null)
{
if (WebSession.Request.RequestLocked)
{ {
throw new Exception("You cannot call this function after request is made to server."); var response = new GenericResponse(status);
} response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseHeaders.AddHeaders(headers);
if (html == null) response.ResponseBody = response.Encoding.GetBytes(html ?? string.Empty);
{
html = string.Empty;
}
var result = Encoding.Default.GetBytes(html);
await GenericResponse(result, headers, status); await Respond(response);
} }
/// <summary> /// <summary>
...@@ -497,25 +475,17 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -497,25 +475,17 @@ namespace Titanium.Web.Proxy.EventArguments
/// and ignore the request /// and ignore the request
/// </summary> /// </summary>
/// <param name="result"></param> /// <param name="result"></param>
/// <param name="headers"></param>
/// <param name="status"></param> /// <param name="status"></param>
/// <param name="headers"></param>
/// <returns></returns> /// <returns></returns>
public async Task GenericResponse(byte[] result, Dictionary<string, HttpHeader> headers, HttpStatusCode status) public async Task GenericResponse(byte[] result, HttpStatusCode status, Dictionary<string, HttpHeader> headers)
{ {
var response = new GenericResponse(status); var response = new GenericResponse(status);
if (headers != null && headers.Count > 0)
{
response.ResponseHeaders = headers;
}
response.HttpVersion = WebSession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseHeaders.AddHeaders(headers);
response.ResponseBody = result; response.ResponseBody = result;
await Respond(response); await Respond(response);
WebSession.Request.CancelRequest = true;
} }
/// <summary> /// <summary>
...@@ -526,19 +496,21 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -526,19 +496,21 @@ namespace Titanium.Web.Proxy.EventArguments
public async Task Redirect(string url) public async Task Redirect(string url)
{ {
var response = new RedirectResponse(); var response = new RedirectResponse();
response.HttpVersion = WebSession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseHeaders.Add("Location", new HttpHeader("Location", url)); response.ResponseHeaders.AddHeader("Location", url);
response.ResponseBody = Encoding.ASCII.GetBytes(string.Empty); response.ResponseBody = new byte[0];
await Respond(response); await Respond(response);
WebSession.Request.CancelRequest = true;
} }
/// a generic responder method /// a generic responder method
public async Task Respond(Response response) public async Task Respond(Response response)
{ {
if (WebSession.Request.RequestLocked)
{
throw new Exception("You cannot call this function after request is made to server.");
}
WebSession.Request.RequestLocked = true; WebSession.Request.RequestLocked = true;
response.ResponseLocked = true; response.ResponseLocked = true;
...@@ -547,6 +519,8 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -547,6 +519,8 @@ namespace Titanium.Web.Proxy.EventArguments
WebSession.Response = response; WebSession.Response = response;
await httpResponseHandler(this); await httpResponseHandler(this);
WebSession.Request.CancelRequest = true;
} }
/// <summary> /// <summary>
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.EventArguments
{
public class TunnelConnectSessionEventArgs : SessionEventArgs
{
public bool IsHttpsConnect { get; set; }
public TunnelConnectSessionEventArgs(ProxyEndPoint endPoint) : base(0, endPoint, null)
{
}
}
}
...@@ -9,8 +9,7 @@ ...@@ -9,8 +9,7 @@
/// Constructor. /// Constructor.
/// </summary> /// </summary>
/// <param name="message"></param> /// <param name="message"></param>
public BodyNotFoundException(string message) public BodyNotFoundException(string message) : base(message)
: base(message)
{ {
} }
} }
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Extensions
{
internal static class DotNetStandardExtensions
{
#if NET45
/// <summary>
/// Disposes the specified client.
/// Int .NET framework 4.5 the TcpClient class has no Dispose method,
/// it is available from .NET 4.6, see
/// https://msdn.microsoft.com/en-us/library/dn823304(v=vs.110).aspx
/// </summary>
/// <param name="client">The client.</param>
internal static void Dispose(this TcpClient client)
{
client.Close();
}
/// <summary>
/// Disposes the specified store.
/// Int .NET framework 4.5 the X509Store class has no Dispose method,
/// it is available from .NET 4.6, see
/// https://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.x509store.dispose(v=vs.110).aspx
/// </summary>
/// <param name="store">The store.</param>
internal static void Dispose(this X509Store store)
{
store.Close();
}
#endif
}
}
...@@ -7,8 +7,8 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -7,8 +7,8 @@ namespace Titanium.Web.Proxy.Extensions
{ {
public static void InvokeParallel<T>(this Func<object, T, Task> callback, object sender, T args) public static void InvokeParallel<T>(this Func<object, T, Task> callback, object sender, T args)
{ {
Delegate[] invocationList = callback.GetInvocationList(); var invocationList = callback.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length]; var handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++) for (int i = 0; i < invocationList.Length; i++)
{ {
...@@ -18,17 +18,30 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -18,17 +18,30 @@ namespace Titanium.Web.Proxy.Extensions
Task.WhenAll(handlerTasks).Wait(); Task.WhenAll(handlerTasks).Wait();
} }
public static async Task InvokeParallelAsync<T>(this Func<object, T, Task> callback, object sender, T args) public static async Task InvokeParallelAsync<T>(this Func<object, T, Task> callback, object sender, T args, Action<Exception> exceptionFunc)
{ {
Delegate[] invocationList = callback.GetInvocationList(); var invocationList = callback.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length]; var handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++) for (int i = 0; i < invocationList.Length; i++)
{ {
handlerTasks[i] = ((Func<object, T, Task>)invocationList[i])(sender, args); handlerTasks[i] = InvokeAsync((Func<object, T, Task>)invocationList[i], sender, args, exceptionFunc);
} }
await Task.WhenAll(handlerTasks); await Task.WhenAll(handlerTasks);
} }
private static async Task InvokeAsync<T>(Func<object, T, Task> callback, object sender, T args, Action<Exception> exceptionFunc)
{
try
{
await callback(sender, args);
}
catch (Exception ex)
{
var ex2 = new Exception("Exception thrown in user event", ex);
exceptionFunc(ex2);
}
}
} }
} }
using System; using System.Text;
using System.Text;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
......
using System; using System.Text;
using System.Text;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
......
using System.Globalization; using System;
using System.Globalization;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
...@@ -30,6 +31,25 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -30,6 +31,25 @@ namespace Titanium.Web.Proxy.Extensions
await input.CopyToAsync(output); await input.CopyToAsync(output);
} }
internal static async Task CopyToAsync(this Stream input, Stream output, Action<byte[], int, int> onCopy)
{
byte[] buffer = new byte[81920];
while (true)
{
int num = await input.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
int bytesRead;
if ((bytesRead = num) != 0)
{
await output.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false);
onCopy?.Invoke(buffer, 0, bytesRead);
}
else
{
break;
}
}
}
/// <summary> /// <summary>
/// copies the specified bytes to the stream from the input stream /// copies the specified bytes to the stream from the input stream
/// </summary> /// </summary>
...@@ -39,7 +59,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -39,7 +59,7 @@ namespace Titanium.Web.Proxy.Extensions
/// <returns></returns> /// <returns></returns>
internal static async Task CopyBytesToStream(this CustomBinaryReader streamReader, Stream stream, long totalBytesToRead) internal static async Task CopyBytesToStream(this CustomBinaryReader streamReader, Stream stream, long totalBytesToRead)
{ {
byte[] buffer = streamReader.Buffer; var buffer = streamReader.Buffer;
long remainingBytes = totalBytesToRead; long remainingBytes = totalBytesToRead;
while (remainingBytes > 0) while (remainingBytes > 0)
...@@ -72,8 +92,8 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -72,8 +92,8 @@ namespace Titanium.Web.Proxy.Extensions
{ {
while (true) while (true)
{ {
var chuchkHead = await clientStreamReader.ReadLineAsync(); string chuchkHead = await clientStreamReader.ReadLineAsync();
var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber); int chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);
if (chunkSize != 0) if (chunkSize != 0)
{ {
...@@ -119,7 +139,8 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -119,7 +139,8 @@ namespace Titanium.Web.Proxy.Extensions
/// <param name="isChunked"></param> /// <param name="isChunked"></param>
/// <param name="contentLength"></param> /// <param name="contentLength"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task WriteResponseBody(this CustomBinaryReader inStreamReader, int bufferSize, Stream outStream, bool isChunked, long contentLength) internal static async Task WriteResponseBody(this CustomBinaryReader inStreamReader, int bufferSize, Stream outStream, bool isChunked,
long contentLength)
{ {
if (!isChunked) if (!isChunked)
{ {
...@@ -147,8 +168,8 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -147,8 +168,8 @@ namespace Titanium.Web.Proxy.Extensions
{ {
while (true) while (true)
{ {
var chunkHead = await inStreamReader.ReadLineAsync(); string chunkHead = await inStreamReader.ReadLineAsync();
var chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber); int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
if (chunkSize != 0) if (chunkSize != 0)
{ {
......
...@@ -13,7 +13,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -13,7 +13,7 @@ namespace Titanium.Web.Proxy.Extensions
internal static bool IsConnected(this Socket client) internal static bool IsConnected(this Socket client)
{ {
// This is how you can determine whether a socket is still connected. // This is how you can determine whether a socket is still connected.
var blockingState = client.Blocking; bool blockingState = client.Blocking;
try try
{ {
...@@ -26,7 +26,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -26,7 +26,7 @@ namespace Titanium.Web.Proxy.Extensions
catch (SocketException e) catch (SocketException e)
{ {
// 10035 == WSAEWOULDBLOCK // 10035 == WSAEWOULDBLOCK
return e.NativeErrorCode.Equals(10035); return e.SocketErrorCode == SocketError.WouldBlock;
} }
finally finally
{ {
...@@ -34,6 +34,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -34,6 +34,7 @@ namespace Titanium.Web.Proxy.Extensions
} }
} }
#if NET45
/// <summary> /// <summary>
/// Gets the local port from a native TCP row object. /// Gets the local port from a native TCP row object.
/// </summary> /// </summary>
...@@ -53,5 +54,6 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -53,5 +54,6 @@ namespace Titanium.Web.Proxy.Extensions
{ {
return (tcpRow.remotePort1 << 8) + tcpRow.remotePort2 + (tcpRow.remotePort3 << 24) + (tcpRow.remotePort4 << 16); return (tcpRow.remotePort1 << 8) + tcpRow.remotePort2 + (tcpRow.remotePort3 << 24) + (tcpRow.remotePort4 << 16);
} }
#endif
} }
} }
using System; using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
......
...@@ -34,7 +34,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -34,7 +34,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <returns></returns> /// <returns></returns>
internal async Task<string> ReadLineAsync() internal async Task<string> ReadLineAsync()
{ {
var lastChar = default(byte); byte lastChar = default(byte);
int bufferDataLength = 0; int bufferDataLength = 0;
...@@ -43,7 +43,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -43,7 +43,7 @@ namespace Titanium.Web.Proxy.Helpers
while (stream.DataAvailable || await stream.FillBufferAsync()) while (stream.DataAvailable || await stream.FillBufferAsync())
{ {
var newChar = stream.ReadByteFromBuffer(); byte newChar = stream.ReadByteFromBuffer();
buffer[bufferDataLength] = newChar; buffer[bufferDataLength] = newChar;
//if new line //if new line
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Helpers
{
class CustomBufferedPeekStream
{
private readonly CustomBufferedStream baseStream;
private int position;
public CustomBufferedPeekStream(CustomBufferedStream baseStream, int startPosition = 0)
{
this.baseStream = baseStream;
position = startPosition;
}
public int Available => baseStream.Available - position;
public async Task<bool> EnsureBufferLength(int length)
{
var val = await baseStream.PeekByteAsync(position + length - 1);
return val != -1;
}
public byte ReadByte()
{
return baseStream.PeekByteFromBuffer(position++);
}
public int ReadInt16()
{
int i1 = ReadByte();
int i2 = ReadByte();
return (i1 << 8) + i2;
}
public int ReadInt24()
{
int i1 = ReadByte();
int i2 = ReadByte();
int i3 = ReadByte();
return (i1 << 16) + (i2 << 8) + i3;
}
public byte[] ReadBytes(int length)
{
var buffer = new byte[length];
for (int i = 0; i < buffer.Length; i++)
{
buffer[i] = ReadByte();
}
return buffer;
}
}
}
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.Remoting;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
...@@ -14,7 +13,9 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -14,7 +13,9 @@ namespace Titanium.Web.Proxy.Helpers
/// <seealso cref="System.IO.Stream" /> /// <seealso cref="System.IO.Stream" />
internal class CustomBufferedStream : Stream internal class CustomBufferedStream : Stream
{ {
#if NET45
private AsyncCallback readCallback; private AsyncCallback readCallback;
#endif
private readonly Stream baseStream; private readonly Stream baseStream;
...@@ -35,7 +36,9 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -35,7 +36,9 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="bufferSize">Size of the buffer.</param> /// <param name="bufferSize">Size of the buffer.</param>
public CustomBufferedStream(Stream baseStream, int bufferSize) public CustomBufferedStream(Stream baseStream, int bufferSize)
{ {
readCallback = new AsyncCallback(ReadCallback); #if NET45
readCallback = ReadCallback;
#endif
this.baseStream = baseStream; this.baseStream = baseStream;
streamBuffer = BufferPool.GetBuffer(bufferSize); streamBuffer = BufferPool.GetBuffer(bufferSize);
} }
...@@ -111,6 +114,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -111,6 +114,7 @@ namespace Titanium.Web.Proxy.Helpers
baseStream.Write(buffer, offset, count); baseStream.Write(buffer, offset, count);
} }
#if NET45
/// <summary> /// <summary>
/// Begins an asynchronous read operation. (Consider using <see cref="M:System.IO.Stream.ReadAsync(System.Byte[],System.Int32,System.Int32)" /> instead; see the Remarks section.) /// Begins an asynchronous read operation. (Consider using <see cref="M:System.IO.Stream.ReadAsync(System.Byte[],System.Int32,System.Int32)" /> instead; see the Remarks section.)
/// </summary> /// </summary>
...@@ -163,6 +167,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -163,6 +167,7 @@ namespace Titanium.Web.Proxy.Helpers
OnDataSent(buffer, offset, count); OnDataSent(buffer, offset, count);
return baseStream.BeginWrite(buffer, offset, count, callback, state); return baseStream.BeginWrite(buffer, offset, count, callback, state);
} }
#endif
/// <summary> /// <summary>
/// Asynchronously reads the bytes from the current stream and writes them to another stream, using a specified buffer size and cancellation token. /// Asynchronously reads the bytes from the current stream and writes them to another stream, using a specified buffer size and cancellation token.
...@@ -184,18 +189,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -184,18 +189,7 @@ namespace Titanium.Web.Proxy.Helpers
await base.CopyToAsync(destination, bufferSize, cancellationToken); await base.CopyToAsync(destination, bufferSize, cancellationToken);
} }
/// <summary> #if NET45
/// Creates an object that contains all the relevant information required to generate a proxy used to communicate with a remote object.
/// </summary>
/// <param name="requestedType">The <see cref="T:System.Type" /> of the object that the new <see cref="T:System.Runtime.Remoting.ObjRef" /> will reference.</param>
/// <returns>
/// Information required to generate a proxy.
/// </returns>
public override ObjRef CreateObjRef(Type requestedType)
{
return baseStream.CreateObjRef(requestedType);
}
/// <summary> /// <summary>
/// Waits for the pending asynchronous read to complete. (Consider using <see cref="M:System.IO.Stream.ReadAsync(System.Byte[],System.Int32,System.Int32)" /> instead; see the Remarks section.) /// Waits for the pending asynchronous read to complete. (Consider using <see cref="M:System.IO.Stream.ReadAsync(System.Byte[],System.Int32,System.Int32)" /> instead; see the Remarks section.)
/// </summary> /// </summary>
...@@ -222,6 +216,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -222,6 +216,7 @@ namespace Titanium.Web.Proxy.Helpers
{ {
baseStream.EndWrite(asyncResult); baseStream.EndWrite(asyncResult);
} }
#endif
/// <summary> /// <summary>
/// Asynchronously clears all buffers for this stream, causes any buffered data to be written to the underlying device, and monitors cancellation requests. /// Asynchronously clears all buffers for this stream, causes any buffered data to be written to the underlying device, and monitors cancellation requests.
...@@ -235,17 +230,6 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -235,17 +230,6 @@ namespace Titanium.Web.Proxy.Helpers
return baseStream.FlushAsync(cancellationToken); return baseStream.FlushAsync(cancellationToken);
} }
/// <summary>
/// Obtains a lifetime service object to control the lifetime policy for this instance.
/// </summary>
/// <returns>
/// An object of type <see cref="T:System.Runtime.Remoting.Lifetime.ILease" /> used to control the lifetime policy for this instance. This is the current lifetime service object for this instance if one exists; otherwise, a new lifetime service object initialized to the value of the <see cref="P:System.Runtime.Remoting.Lifetime.LifetimeServices.LeaseManagerPollTime" /> property.
/// </returns>
public override object InitializeLifetimeService()
{
return baseStream.InitializeLifetimeService();
}
/// <summary> /// <summary>
/// Asynchronously reads a sequence of bytes from the current stream, /// Asynchronously reads a sequence of bytes from the current stream,
/// advances the position within the stream by the number of bytes read, /// advances the position within the stream by the number of bytes read,
...@@ -306,6 +290,31 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -306,6 +290,31 @@ namespace Titanium.Web.Proxy.Helpers
return streamBuffer[bufferPos++]; return streamBuffer[bufferPos++];
} }
public async Task<int> PeekByteAsync(int index)
{
if (Available <= index)
{
await FillBufferAsync();
}
if (Available <= index)
{
return -1;
}
return streamBuffer[bufferPos + index];
}
public byte PeekByteFromBuffer(int index)
{
if (bufferLength <= index)
{
throw new Exception("Index is out of buffer size");
}
return streamBuffer[bufferPos + index];
}
public byte ReadByteFromBuffer() public byte ReadByteFromBuffer()
{ {
if (bufferLength == 0) if (bufferLength == 0)
...@@ -359,15 +368,16 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -359,15 +368,16 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
if(!disposed) if (!disposed)
{ {
disposed = true; disposed = true;
baseStream.Dispose(); baseStream.Dispose();
BufferPool.ReturnBuffer(streamBuffer); BufferPool.ReturnBuffer(streamBuffer);
streamBuffer = null; streamBuffer = null;
#if NET45
readCallback = null; readCallback = null;
#endif
} }
} }
/// <summary> /// <summary>
...@@ -397,6 +407,8 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -397,6 +407,8 @@ namespace Titanium.Web.Proxy.Helpers
public bool DataAvailable => bufferLength > 0; public bool DataAvailable => bufferLength > 0;
public int Available => bufferLength;
/// <summary> /// <summary>
/// When overridden in a derived class, gets or sets the position within the current stream. /// When overridden in a derived class, gets or sets the position within the current stream.
/// </summary> /// </summary>
...@@ -429,14 +441,22 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -429,14 +441,22 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary> /// </summary>
public bool FillBuffer() public bool FillBuffer()
{ {
bufferLength = baseStream.Read(streamBuffer, 0, streamBuffer.Length);
bufferPos = 0;
if (bufferLength > 0) if (bufferLength > 0)
{ {
OnDataReceived(streamBuffer, 0, bufferLength); //normally we fill the buffer only when it is empty, but sometimes we need more data
//move the remanining data to the beginning of the buffer
Buffer.BlockCopy(streamBuffer, bufferPos, streamBuffer, 0, bufferLength);
}
bufferPos = 0;
int readBytes = baseStream.Read(streamBuffer, bufferLength, streamBuffer.Length - bufferLength);
if (readBytes > 0)
{
OnDataReceived(streamBuffer, bufferLength, readBytes);
bufferLength += readBytes;
} }
return bufferLength > 0; return readBytes > 0;
} }
/// <summary> /// <summary>
...@@ -455,14 +475,22 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -455,14 +475,22 @@ namespace Titanium.Web.Proxy.Helpers
/// <returns></returns> /// <returns></returns>
public async Task<bool> FillBufferAsync(CancellationToken cancellationToken) public async Task<bool> FillBufferAsync(CancellationToken cancellationToken)
{ {
bufferLength = await baseStream.ReadAsync(streamBuffer, 0, streamBuffer.Length, cancellationToken);
bufferPos = 0;
if (bufferLength > 0) if (bufferLength > 0)
{ {
OnDataReceived(streamBuffer, 0, bufferLength); //normally we fill the buffer only when it is empty, but sometimes we need more data
//move the remanining data to the beginning of the buffer
Buffer.BlockCopy(streamBuffer, bufferPos, streamBuffer, 0, bufferLength);
}
bufferPos = 0;
int readBytes = await baseStream.ReadAsync(streamBuffer, bufferLength, streamBuffer.Length - bufferLength, cancellationToken);
if (readBytes > 0)
{
OnDataReceived(streamBuffer, bufferLength, readBytes);
bufferLength += readBytes;
} }
return bufferLength > 0; return readBytes > 0;
} }
private class ReadAsyncResult : IAsyncResult private class ReadAsyncResult : IAsyncResult
......
...@@ -16,9 +16,9 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -16,9 +16,9 @@ namespace Titanium.Web.Proxy.Helpers
try try
{ {
var myProfileDirectory = var myProfileDirectory =
new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\Mozilla\\Firefox\\Profiles\\")
"\\Mozilla\\Firefox\\Profiles\\").GetDirectories("*.default"); .GetDirectories("*.default");
var myFfPrefFile = myProfileDirectory[0].FullName + "\\prefs.js"; string myFfPrefFile = myProfileDirectory[0].FullName + "\\prefs.js";
if (!File.Exists(myFfPrefFile)) if (!File.Exists(myFfPrefFile))
{ {
return; return;
...@@ -26,18 +26,17 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -26,18 +26,17 @@ namespace Titanium.Web.Proxy.Helpers
// We have a pref file so let''s make sure it has the proxy setting // We have a pref file so let''s make sure it has the proxy setting
var myReader = new StreamReader(myFfPrefFile); var myReader = new StreamReader(myFfPrefFile);
var myPrefContents = myReader.ReadToEnd(); string myPrefContents = myReader.ReadToEnd();
myReader.Close(); myReader.Close();
for (int i = 0; i <= 4; i++) for (int i = 0; i <= 4; i++)
{ {
var searchStr = $"user_pref(\"network.proxy.type\", {i});"; string searchStr = $"user_pref(\"network.proxy.type\", {i});";
if (myPrefContents.Contains(searchStr)) if (myPrefContents.Contains(searchStr))
{ {
// Add the proxy enable line and write it back to the file // Add the proxy enable line and write it back to the file
myPrefContents = myPrefContents.Replace(searchStr, myPrefContents = myPrefContents.Replace(searchStr, "user_pref(\"network.proxy.type\", 5);");
"user_pref(\"network.proxy.type\", 5);");
} }
} }
......
...@@ -20,7 +20,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -20,7 +20,7 @@ namespace Titanium.Web.Proxy.Helpers
//extract the encoding by finding the charset //extract the encoding by finding the charset
var parameters = contentType.Split(ProxyConstants.SemiColonSplit); var parameters = contentType.Split(ProxyConstants.SemiColonSplit);
foreach (var parameter in parameters) foreach (string parameter in parameters)
{ {
var encodingSplit = parameter.Split(ProxyConstants.EqualSplit, 2); var encodingSplit = parameter.Split(ProxyConstants.EqualSplit, 2);
if (encodingSplit.Length == 2 && encodingSplit[0].Trim().Equals("charset", StringComparison.CurrentCultureIgnoreCase)) if (encodingSplit.Length == 2 && encodingSplit[0].Trim().Equals("charset", StringComparison.CurrentCultureIgnoreCase))
...@@ -66,9 +66,8 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -66,9 +66,8 @@ namespace Titanium.Web.Proxy.Helpers
if (hostname.Split(ProxyConstants.DotSplit).Length > 2) if (hostname.Split(ProxyConstants.DotSplit).Length > 2)
{ {
int idx = hostname.IndexOf(ProxyConstants.DotSplit); int idx = hostname.IndexOf(ProxyConstants.DotSplit);
var rootDomain = hostname.Substring(idx + 1); string rootDomain = hostname.Substring(idx + 1);
return "*." + rootDomain; return "*." + rootDomain;
} }
//return as it is //return as it is
......
using System;
using System.Runtime.InteropServices;
namespace Titanium.Web.Proxy.Helpers
{
internal partial class NativeMethods
{
[DllImport("wininet.dll")]
internal static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int dwBufferLength);
[DllImport("kernel32.dll")]
internal static extern IntPtr GetConsoleWindow();
// Keeps it from getting garbage collected
internal static ConsoleEventDelegate Handler;
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetConsoleCtrlHandler(ConsoleEventDelegate callback, bool add);
// Pinvoke
internal delegate bool ConsoleEventDelegate(int eventType);
}
}
\ No newline at end of file
using System;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
namespace Titanium.Web.Proxy.Helpers
{
internal partial class NativeMethods
{
internal const int AfInet = 2;
internal const int AfInet6 = 23;
internal enum TcpTableType
{
BasicListener,
BasicConnections,
BasicAll,
OwnerPidListener,
OwnerPidConnections,
OwnerPidAll,
OwnerModuleListener,
OwnerModuleConnections,
OwnerModuleAll,
}
/// <summary>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa366921.aspx"/>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct TcpTable
{
public uint length;
public TcpRow row;
}
/// <summary>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct TcpRow
{
public TcpState state;
public uint localAddr;
public byte localPort1;
public byte localPort2;
public byte localPort3;
public byte localPort4;
public uint remoteAddr;
public byte remotePort1;
public byte remotePort2;
public byte remotePort3;
public byte remotePort4;
public int owningPid;
}
/// <summary>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/>
/// </summary>
[DllImport("iphlpapi.dll", SetLastError = true)]
internal static extern uint GetExtendedTcpTable(IntPtr tcpTable, ref int size, bool sort, int ipVersion, int tableClass, int reserved);
}
}
\ No newline at end of file
...@@ -6,6 +6,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -6,6 +6,7 @@ namespace Titanium.Web.Proxy.Helpers
{ {
internal class NetworkHelper internal class NetworkHelper
{ {
#if NET45
private static int FindProcessIdFromLocalPort(int port, IpVersion ipVersion) private static int FindProcessIdFromLocalPort(int port, IpVersion ipVersion)
{ {
var tcpRow = TcpHelper.GetTcpRowByLocalPort(ipVersion, port); var tcpRow = TcpHelper.GetTcpRowByLocalPort(ipVersion, port);
...@@ -15,7 +16,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -15,7 +16,7 @@ namespace Titanium.Web.Proxy.Helpers
internal static int GetProcessIdFromPort(int port, bool ipV6Enabled) internal static int GetProcessIdFromPort(int port, bool ipV6Enabled)
{ {
var processId = FindProcessIdFromLocalPort(port, IpVersion.Ipv4); int processId = FindProcessIdFromLocalPort(port, IpVersion.Ipv4);
if (processId > 0 && !ipV6Enabled) if (processId > 0 && !ipV6Enabled)
{ {
...@@ -43,10 +44,10 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -43,10 +44,10 @@ namespace Titanium.Web.Proxy.Helpers
{ {
bool isLocalhost = false; bool isLocalhost = false;
IPHostEntry localhost = Dns.GetHostEntry("127.0.0.1"); var localhost = Dns.GetHostEntry("127.0.0.1");
if (hostName == localhost.HostName) if (hostName == localhost.HostName)
{ {
IPHostEntry hostEntry = Dns.GetHostEntry(hostName); var hostEntry = Dns.GetHostEntry(hostName);
isLocalhost = hostEntry.AddressList.Any(IPAddress.IsLoopback); isLocalhost = hostEntry.AddressList.Any(IPAddress.IsLoopback);
} }
...@@ -74,5 +75,56 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -74,5 +75,56 @@ namespace Titanium.Web.Proxy.Helpers
return isLocalhost; return isLocalhost;
} }
#else
/// <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)
{
// get local IP addresses
var localIPs = Dns.GetHostAddressesAsync(Dns.GetHostName()).Result;
// test if any host IP equals to any local IP or to localhost
return IPAddress.IsLoopback(address) || localIPs.Contains(address);
}
internal static bool IsLocalIpAddress(string hostName)
{
bool isLocalhost = false;
var localhost = Dns.GetHostEntryAsync("127.0.0.1").Result;
if (hostName == localhost.HostName)
{
var hostEntry = Dns.GetHostEntryAsync(hostName).Result;
isLocalhost = hostEntry.AddressList.Any(IPAddress.IsLoopback);
}
if (!isLocalhost)
{
localhost = Dns.GetHostEntryAsync(Dns.GetHostName()).Result;
IPAddress ipAddress;
if (IPAddress.TryParse(hostName, out ipAddress))
isLocalhost = localhost.AddressList.Any(x => x.Equals(ipAddress));
if (!isLocalhost)
{
try
{
var hostEntry = Dns.GetHostEntryAsync(hostName).Result;
isLocalhost = localhost.AddressList.Any(x => hostEntry.AddressList.Any(x.Equals));
}
catch (SocketException)
{
}
}
}
return isLocalhost;
}
#endif
} }
} }
...@@ -3,7 +3,6 @@ using System.Collections.Generic; ...@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
...@@ -37,14 +36,14 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -37,14 +36,14 @@ namespace Titanium.Web.Proxy.Helpers
if (proxyServer != null) if (proxyServer != null)
{ {
Proxies = GetSystemProxyValues(proxyServer).ToDictionary(x=>x.ProtocolType); Proxies = GetSystemProxyValues(proxyServer).ToDictionary(x => x.ProtocolType);
} }
if (proxyOverride != null) if (proxyOverride != null)
{ {
var overrides = proxyOverride.Split(';'); var overrides = proxyOverride.Split(';');
var overrides2 = new List<string>(); var overrides2 = new List<string>();
foreach (var overrideHost in overrides) foreach (string overrideHost in overrides)
{ {
if (overrideHost == "<-loopback>") if (overrideHost == "<-loopback>")
{ {
...@@ -71,7 +70,8 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -71,7 +70,8 @@ namespace Titanium.Web.Proxy.Helpers
private static string BypassStringEscape(string rawString) private static string BypassStringEscape(string rawString)
{ {
Match match = new Regex("^(?<scheme>.*://)?(?<host>[^:]*)(?<port>:[0-9]{1,5})?$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant).Match(rawString); var match =
new Regex("^(?<scheme>.*://)?(?<host>[^:]*)(?<port>:[0-9]{1,5})?$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant).Match(rawString);
string empty1; string empty1;
string rawString1; string rawString1;
string empty2; string empty2;
...@@ -127,11 +127,11 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -127,11 +127,11 @@ namespace Titanium.Web.Proxy.Helpers
} }
ProxyProtocolType? protocolType = null; ProxyProtocolType? protocolType = null;
if (protocolTypeStr.Equals("http", StringComparison.InvariantCultureIgnoreCase)) if (protocolTypeStr.Equals(Proxy.ProxyServer.UriSchemeHttp, StringComparison.InvariantCultureIgnoreCase))
{ {
protocolType = ProxyProtocolType.Http; protocolType = ProxyProtocolType.Http;
} }
else if (protocolTypeStr.Equals("https", StringComparison.InvariantCultureIgnoreCase)) else if (protocolTypeStr.Equals(Proxy.ProxyServer.UriSchemeHttps, StringComparison.InvariantCultureIgnoreCase))
{ {
protocolType = ProxyProtocolType.Https; protocolType = ProxyProtocolType.Https;
} }
...@@ -174,7 +174,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -174,7 +174,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <returns></returns> /// <returns></returns>
private static HttpSystemProxyValue ParseProxyValue(string value) private static HttpSystemProxyValue ParseProxyValue(string value)
{ {
var tmp = Regex.Replace(value, @"\s+", " ").Trim(); string tmp = Regex.Replace(value, @"\s+", " ").Trim();
int equalsIndex = tmp.IndexOf("=", StringComparison.InvariantCulture); int equalsIndex = tmp.IndexOf("=", StringComparison.InvariantCulture);
if (equalsIndex >= 0) if (equalsIndex >= 0)
......
...@@ -11,8 +11,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -11,8 +11,7 @@ namespace Titanium.Web.Proxy.Helpers
/// cache for mono runtime check /// cache for mono runtime check
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
private static Lazy<bool> isRunningOnMono private static readonly Lazy<bool> isRunningOnMono = new Lazy<bool>(() => Type.GetType("Mono.Runtime") != null);
= new Lazy<bool>(()=> Type.GetType("Mono.Runtime") != null);
/// <summary> /// <summary>
/// Is running on Mono? /// Is running on Mono?
......
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Win32; using Microsoft.Win32;
// Helper classes for setting system proxy settings // Helper classes for setting system proxy settings
...@@ -31,25 +29,6 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -31,25 +29,6 @@ namespace Titanium.Web.Proxy.Helpers
AllHttp = Http | Https, AllHttp = Http | Https,
} }
internal partial class NativeMethods
{
[DllImport("wininet.dll")]
internal static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer,
int dwBufferLength);
[DllImport("kernel32.dll")]
internal static extern IntPtr GetConsoleWindow();
// Keeps it from getting garbage collected
internal static ConsoleEventDelegate Handler;
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetConsoleCtrlHandler(ConsoleEventDelegate callback, bool add);
// Pinvoke
internal delegate bool ConsoleEventDelegate(int eventType);
}
internal class HttpSystemProxyValue internal class HttpSystemProxyValue
{ {
internal string HostName { get; set; } internal string HostName { get; set; }
...@@ -64,10 +43,10 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -64,10 +43,10 @@ namespace Titanium.Web.Proxy.Helpers
switch (ProtocolType) switch (ProtocolType)
{ {
case ProxyProtocolType.Http: case ProxyProtocolType.Http:
protocol = "http"; protocol = ProxyServer.UriSchemeHttp;
break; break;
case ProxyProtocolType.Https: case ProxyProtocolType.Https:
protocol = "https"; protocol = Proxy.ProxyServer.UriSchemeHttps;
break; break;
default: default:
throw new Exception("Unsupported protocol type"); throw new Exception("Unsupported protocol type");
...@@ -129,7 +108,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -129,7 +108,7 @@ namespace Titanium.Web.Proxy.Helpers
SaveOriginalProxyConfiguration(reg); SaveOriginalProxyConfiguration(reg);
PrepareRegistry(reg); PrepareRegistry(reg);
var exisitingContent = reg.GetValue(regProxyServer) as string; string exisitingContent = reg.GetValue(regProxyServer) as string;
var existingSystemProxyValues = ProxyInfo.GetSystemProxyValues(exisitingContent); var existingSystemProxyValues = ProxyInfo.GetSystemProxyValues(exisitingContent);
existingSystemProxyValues.RemoveAll(x => (protocolType & x.ProtocolType) != 0); existingSystemProxyValues.RemoveAll(x => (protocolType & x.ProtocolType) != 0);
if ((protocolType & ProxyProtocolType.Http) != 0) if ((protocolType & ProxyProtocolType.Http) != 0)
...@@ -175,7 +154,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -175,7 +154,7 @@ namespace Titanium.Web.Proxy.Helpers
if (reg.GetValue(regProxyServer) != null) if (reg.GetValue(regProxyServer) != null)
{ {
var exisitingContent = reg.GetValue(regProxyServer) as string; string exisitingContent = reg.GetValue(regProxyServer) as string;
var existingSystemProxyValues = ProxyInfo.GetSystemProxyValues(exisitingContent); var existingSystemProxyValues = ProxyInfo.GetSystemProxyValues(exisitingContent);
existingSystemProxyValues.RemoveAll(x => (protocolType & x.ProtocolType) != 0); existingSystemProxyValues.RemoveAll(x => (protocolType & x.ProtocolType) != 0);
...@@ -305,11 +284,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -305,11 +284,7 @@ namespace Titanium.Web.Proxy.Helpers
private ProxyInfo GetProxyInfoFromRegistry(RegistryKey reg) private ProxyInfo GetProxyInfoFromRegistry(RegistryKey reg)
{ {
var pi = new ProxyInfo( var pi = new ProxyInfo(null, reg.GetValue(regAutoConfigUrl) as string, reg.GetValue(regProxyEnable) as int?, reg.GetValue(regProxyServer) as string,
null,
reg.GetValue(regAutoConfigUrl) as string,
reg.GetValue(regProxyEnable) as int?,
reg.GetValue(regProxyServer) as string,
reg.GetValue(regProxyOverride) as string); reg.GetValue(regProxyOverride) as string);
return pi; return pi;
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.Tcp; using Titanium.Web.Proxy.Network.Tcp;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
...@@ -20,75 +16,21 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -20,75 +16,21 @@ namespace Titanium.Web.Proxy.Helpers
Ipv6 = 2, Ipv6 = 2,
} }
internal partial class NativeMethods
{
internal const int AfInet = 2;
internal const int AfInet6 = 23;
internal enum TcpTableType
{
BasicListener,
BasicConnections,
BasicAll,
OwnerPidListener,
OwnerPidConnections,
OwnerPidAll,
OwnerModuleListener,
OwnerModuleConnections,
OwnerModuleAll,
}
/// <summary>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa366921.aspx"/>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct TcpTable
{
public uint length;
public TcpRow row;
}
/// <summary>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct TcpRow
{
public TcpState state;
public uint localAddr;
public byte localPort1;
public byte localPort2;
public byte localPort3;
public byte localPort4;
public uint remoteAddr;
public byte remotePort1;
public byte remotePort2;
public byte remotePort3;
public byte remotePort4;
public int owningPid;
}
/// <summary>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/>
/// </summary>
[DllImport("iphlpapi.dll", SetLastError = true)]
internal static extern uint GetExtendedTcpTable(IntPtr tcpTable, ref int size, bool sort, int ipVersion, int tableClass, int reserved);
}
internal class TcpHelper internal class TcpHelper
{ {
#if NET45
/// <summary> /// <summary>
/// Gets the extended TCP table. /// Gets the extended TCP table.
/// </summary> /// </summary>
/// <returns>Collection of <see cref="TcpRow"/>.</returns> /// <returns>Collection of <see cref="TcpRow"/>.</returns>
internal static TcpTable GetExtendedTcpTable(IpVersion ipVersion) internal static TcpTable GetExtendedTcpTable(IpVersion ipVersion)
{ {
List<TcpRow> tcpRows = new List<TcpRow>(); var tcpRows = new List<TcpRow>();
IntPtr tcpTable = IntPtr.Zero; var tcpTable = IntPtr.Zero;
int tcpTableLength = 0; int tcpTableLength = 0;
var ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6; int ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6;
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) != 0) if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) != 0)
{ {
...@@ -97,9 +39,9 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -97,9 +39,9 @@ namespace Titanium.Web.Proxy.Helpers
tcpTable = Marshal.AllocHGlobal(tcpTableLength); tcpTable = Marshal.AllocHGlobal(tcpTableLength);
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) == 0) if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) == 0)
{ {
NativeMethods.TcpTable table = (NativeMethods.TcpTable)Marshal.PtrToStructure(tcpTable, typeof(NativeMethods.TcpTable)); var table = (NativeMethods.TcpTable)Marshal.PtrToStructure(tcpTable, typeof(NativeMethods.TcpTable));
IntPtr rowPtr = (IntPtr)((long)tcpTable + Marshal.SizeOf(table.length)); var rowPtr = (IntPtr)((long)tcpTable + Marshal.SizeOf(table.length));
for (int i = 0; i < table.length; ++i) for (int i = 0; i < table.length; ++i)
{ {
...@@ -126,10 +68,10 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -126,10 +68,10 @@ namespace Titanium.Web.Proxy.Helpers
/// <returns><see cref="TcpRow"/>.</returns> /// <returns><see cref="TcpRow"/>.</returns>
internal static TcpRow GetTcpRowByLocalPort(IpVersion ipVersion, int localPort) internal static TcpRow GetTcpRowByLocalPort(IpVersion ipVersion, int localPort)
{ {
IntPtr tcpTable = IntPtr.Zero; var tcpTable = IntPtr.Zero;
int tcpTableLength = 0; int tcpTableLength = 0;
var ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6; int ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6;
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) != 0) if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) != 0)
{ {
...@@ -138,9 +80,9 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -138,9 +80,9 @@ namespace Titanium.Web.Proxy.Helpers
tcpTable = Marshal.AllocHGlobal(tcpTableLength); tcpTable = Marshal.AllocHGlobal(tcpTableLength);
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) == 0) if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) == 0)
{ {
NativeMethods.TcpTable table = (NativeMethods.TcpTable)Marshal.PtrToStructure(tcpTable, typeof(NativeMethods.TcpTable)); var table = (NativeMethods.TcpTable)Marshal.PtrToStructure(tcpTable, typeof(NativeMethods.TcpTable));
IntPtr rowPtr = (IntPtr)((long)tcpTable + Marshal.SizeOf(table.length)); var rowPtr = (IntPtr)((long)tcpTable + Marshal.SizeOf(table.length));
for (int i = 0; i < table.length; ++i) for (int i = 0; i < table.length; ++i)
{ {
...@@ -165,93 +107,27 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -165,93 +107,27 @@ namespace Titanium.Web.Proxy.Helpers
return null; return null;
} }
#endif
/// <summary> /// <summary>
/// relays the input clientStream to the server at the specified host name and port with the given httpCmd and headers as prefix /// relays the input clientStream to the server at the specified host name and port with the given httpCmd and headers as prefix
/// Usefull for websocket requests /// Usefull for websocket requests
/// </summary> /// </summary>
/// <param name="server"></param>
/// <param name="remoteHostName"></param>
/// <param name="remotePort"></param>
/// <param name="httpCmd"></param>
/// <param name="httpVersion"></param>
/// <param name="requestHeaders"></param>
/// <param name="isHttps"></param>
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <param name="tcpConnectionFactory"></param>
/// <param name="connection"></param> /// <param name="connection"></param>
/// <param name="onDataSend"></param>
/// <param name="onDataReceive"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task SendRaw(ProxyServer server, internal static async Task SendRaw(Stream clientStream, TcpConnection connection, Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive)
string remoteHostName, int remotePort,
string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders,
bool isHttps,
Stream clientStream, TcpConnectionFactory tcpConnectionFactory,
TcpConnection connection = null)
{
//prepare the prefix content
StringBuilder sb = null;
if (httpCmd != null || requestHeaders != null)
{
sb = new StringBuilder();
if (httpCmd != null)
{
sb.Append(httpCmd);
sb.Append(ProxyConstants.NewLine);
}
if (requestHeaders != null)
{
foreach (var header in requestHeaders.Select(t => t.Value.ToString()))
{
sb.Append(header);
sb.Append(ProxyConstants.NewLine);
}
}
sb.Append(ProxyConstants.NewLine);
}
bool connectionCreated = false;
TcpConnection tcpConnection;
//create new connection if connection is null
if (connection == null)
{ {
tcpConnection = await tcpConnectionFactory.CreateClient(server, var tunnelStream = connection.Stream;
remoteHostName, remotePort,
httpVersion, isHttps,
null, null);
connectionCreated = true;
}
else
{
tcpConnection = connection;
}
try
{
Stream tunnelStream = tcpConnection.Stream;
//Now async relay all server=>client & client=>server data //Now async relay all server=>client & client=>server data
var sendRelay = clientStream.CopyToAsync(sb?.ToString() ?? string.Empty, tunnelStream); var sendRelay = clientStream.CopyToAsync(tunnelStream, onDataSend);
var receiveRelay = tunnelStream.CopyToAsync(string.Empty, clientStream); var receiveRelay = tunnelStream.CopyToAsync(clientStream, onDataReceive);
await Task.WhenAll(sendRelay, receiveRelay); await Task.WhenAll(sendRelay, receiveRelay);
} }
finally
{
//if connection was null
//then a new connection was created
//so dispose the new connection
if (connectionCreated)
{
tcpConnection.Dispose();
Interlocked.Decrement(ref server.serverConnectionCount);
}
}
}
} }
} }
\ No newline at end of file
...@@ -18,7 +18,8 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp ...@@ -18,7 +18,8 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
internal static extern bool WinHttpSetTimeouts(WinHttpHandle session, int resolveTimeout, int connectTimeout, int sendTimeout, int receiveTimeout); internal static extern bool WinHttpSetTimeouts(WinHttpHandle session, int resolveTimeout, int connectTimeout, int sendTimeout, int receiveTimeout);
[DllImport("winhttp.dll", CharSet = CharSet.Unicode, SetLastError = true)] [DllImport("winhttp.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool WinHttpGetProxyForUrl(WinHttpHandle session, string url, [In] ref WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions, out WINHTTP_PROXY_INFO proxyInfo); internal static extern bool WinHttpGetProxyForUrl(WinHttpHandle session, string url, [In] ref WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions,
out WINHTTP_PROXY_INFO proxyInfo);
[DllImport("winhttp.dll", CharSet = CharSet.Unicode, SetLastError = true)] [DllImport("winhttp.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool WinHttpCloseHandle(IntPtr httpSession); internal static extern bool WinHttpCloseHandle(IntPtr httpSession);
...@@ -62,8 +63,8 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp ...@@ -62,8 +63,8 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
public AutoProxyFlags Flags; public AutoProxyFlags Flags;
public AutoDetectType AutoDetectFlags; public AutoDetectType AutoDetectFlags;
[MarshalAs(UnmanagedType.LPWStr)] public string AutoConfigUrl; [MarshalAs(UnmanagedType.LPWStr)] public string AutoConfigUrl;
private IntPtr lpvReserved; private readonly IntPtr lpvReserved;
private int dwReserved; private readonly int dwReserved;
public bool AutoLogonIfChallenged; public bool AutoLogonIfChallenged;
} }
......
...@@ -26,7 +26,7 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp ...@@ -26,7 +26,7 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
public bool AutomaticallyDetectSettings { get; internal set; } public bool AutomaticallyDetectSettings { get; internal set; }
private WebProxy Proxy { get; set; } private WebProxy proxy { get; set; }
public WinHttpWebProxyFinder() public WinHttpWebProxyFinder()
{ {
...@@ -89,26 +89,26 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp ...@@ -89,26 +89,26 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
return null; return null;
} }
string proxy = proxies[0]; string proxyStr = proxies[0];
int port = 80; int port = 80;
if (proxy.Contains(":")) if (proxyStr.Contains(":"))
{ {
var parts = proxy.Split(new[] { ':' }, 2); var parts = proxyStr.Split(new[] { ':' }, 2);
proxy = parts[0]; proxyStr = parts[0];
port = int.Parse(parts[1]); port = int.Parse(parts[1]);
} }
// TODO: Apply authorization // TODO: Apply authorization
var systemProxy = new ExternalProxy var systemProxy = new ExternalProxy
{ {
HostName = proxy, HostName = proxyStr,
Port = port, Port = port,
}; };
return systemProxy; return systemProxy;
} }
if (Proxy?.IsBypassed(destination) == true) if (proxy?.IsBypassed(destination) == true)
return null; return null;
var protocolType = ProxyInfo.ParseProtocolType(destination.Scheme); var protocolType = ProxyInfo.ParseProtocolType(destination.Scheme);
...@@ -138,7 +138,7 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp ...@@ -138,7 +138,7 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
AutomaticConfigurationScript = pi.AutoConfigUrl == null ? null : new Uri(pi.AutoConfigUrl); AutomaticConfigurationScript = pi.AutoConfigUrl == null ? null : new Uri(pi.AutoConfigUrl);
BypassLoopback = pi.BypassLoopback; BypassLoopback = pi.BypassLoopback;
BypassOnLocal = pi.BypassOnLocal; BypassOnLocal = pi.BypassOnLocal;
Proxy = new WebProxy(new Uri("http://localhost"), BypassOnLocal, pi.BypassList); proxy = new WebProxy(new Uri("http://localhost"), BypassOnLocal, pi.BypassList);
} }
private ProxyInfo GetProxyInfo() private ProxyInfo GetProxyInfo()
......
using System.Threading.Tasks;
using Titanium.Web.Proxy.Ssl;
namespace Titanium.Web.Proxy.Http
{
public class ConnectRequest : Request
{
public ClientHelloInfo ClientHelloInfo { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Http
{
public class ConnectResponse : Response
{
public string ServerHelloInfo { get; set; }
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Http
{
public class HeaderCollection : IEnumerable<HttpHeader>
{
/// <summary>
/// Unique Request header collection
/// </summary>
public Dictionary<string, HttpHeader> Headers { get; set; }
/// <summary>
/// Non Unique headers
/// </summary>
public Dictionary<string, List<HttpHeader>> NonUniqueHeaders { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="HeaderCollection"/> class.
/// </summary>
public HeaderCollection()
{
Headers = new Dictionary<string, HttpHeader>(StringComparer.OrdinalIgnoreCase);
NonUniqueHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// True if header exists
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public bool HeaderExists(string name)
{
return Headers.ContainsKey(name) || NonUniqueHeaders.ContainsKey(name);
}
/// <summary>
/// Returns all headers with given name if exists
/// Returns null if does'nt exist
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public List<HttpHeader> GetHeaders(string name)
{
if (Headers.ContainsKey(name))
{
return new List<HttpHeader>
{
Headers[name]
};
}
if (NonUniqueHeaders.ContainsKey(name))
{
return new List<HttpHeader>(NonUniqueHeaders[name]);
}
return null;
}
/// <summary>
/// Returns all headers
/// </summary>
/// <returns></returns>
public List<HttpHeader> GetAllHeaders()
{
var result = new List<HttpHeader>();
result.AddRange(Headers.Select(x => x.Value));
result.AddRange(NonUniqueHeaders.SelectMany(x => x.Value));
return result;
}
/// <summary>
/// Add a new header with given name and value
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
public void AddHeader(string name, string value)
{
AddHeader(new HttpHeader(name, value));
}
/// <summary>
/// Adds the given header object to Request
/// </summary>
/// <param name="newHeader"></param>
public void AddHeader(HttpHeader newHeader)
{
if (NonUniqueHeaders.ContainsKey(newHeader.Name))
{
NonUniqueHeaders[newHeader.Name].Add(newHeader);
return;
}
if (Headers.ContainsKey(newHeader.Name))
{
var existing = Headers[newHeader.Name];
Headers.Remove(newHeader.Name);
NonUniqueHeaders.Add(newHeader.Name, new List<HttpHeader>
{
existing,
newHeader
});
}
else
{
Headers.Add(newHeader.Name, newHeader);
}
}
/// <summary>
/// Adds the given header objects to Request
/// </summary>
/// <param name="newHeaders"></param>
public void AddHeaders(IEnumerable<HttpHeader> newHeaders)
{
if (newHeaders == null)
{
return;
}
foreach (var header in newHeaders)
{
AddHeader(header);
}
}
/// <summary>
/// Adds the given header objects to Request
/// </summary>
/// <param name="newHeaders"></param>
public void AddHeaders(IEnumerable<KeyValuePair<string, string>> newHeaders)
{
if (newHeaders == null)
{
return;
}
foreach (var header in newHeaders)
{
AddHeader(header.Key, header.Value);
}
}
/// <summary>
/// Adds the given header objects to Request
/// </summary>
/// <param name="newHeaders"></param>
public void AddHeaders(IEnumerable<KeyValuePair<string, HttpHeader>> newHeaders)
{
if (newHeaders == null)
{
return;
}
foreach (var header in newHeaders)
{
if (header.Key != header.Value.Name)
{
throw new Exception("Header name mismatch. Key and the name of the HttpHeader object should be the same.");
}
AddHeader(header.Value);
}
}
/// <summary>
/// removes all headers with given name
/// </summary>
/// <param name="headerName"></param>
/// <returns>True if header was removed
/// False if no header exists with given name</returns>
public bool RemoveHeader(string headerName)
{
bool result = Headers.Remove(headerName);
// do not convert to '||' expression to avoid lazy evaluation
if (NonUniqueHeaders.Remove(headerName))
{
result = true;
}
return result;
}
/// <summary>
/// Removes given header object if it exist
/// </summary>
/// <param name="header">Returns true if header exists and was removed </param>
public bool RemoveHeader(HttpHeader header)
{
if (Headers.ContainsKey(header.Name))
{
if (Headers[header.Name].Equals(header))
{
Headers.Remove(header.Name);
return true;
}
}
else if (NonUniqueHeaders.ContainsKey(header.Name))
{
if (NonUniqueHeaders[header.Name].RemoveAll(x => x.Equals(header)) > 0)
{
return true;
}
}
return false;
}
/// <summary>
/// Removes all the headers.
/// </summary>
public void Clear()
{
Headers.Clear();
NonUniqueHeaders.Clear();
}
internal string GetHeaderValueOrNull(string headerName)
{
HttpHeader header;
if (Headers.TryGetValue(headerName, out header))
{
return header.Value;
}
return null;
}
internal string SetOrAddHeaderValue(string headerName, string value)
{
HttpHeader header;
if (Headers.TryGetValue(headerName, out header))
{
header.Value = value;
}
else
{
Headers.Add(headerName, new HttpHeader(headerName, value));
}
return null;
}
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// An enumerator that can be used to iterate through the collection.
/// </returns>
public IEnumerator<HttpHeader> GetEnumerator()
{
return Headers.Values.Concat(NonUniqueHeaders.Values.SelectMany(x => x)).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
...@@ -8,10 +8,11 @@ namespace Titanium.Web.Proxy.Http ...@@ -8,10 +8,11 @@ namespace Titanium.Web.Proxy.Http
{ {
internal static class HeaderParser internal static class HeaderParser
{ {
internal static async Task ReadHeaders(CustomBinaryReader reader, internal static async Task ReadHeaders(CustomBinaryReader reader, HeaderCollection headerCollection)
Dictionary<string, List<HttpHeader>> nonUniqueResponseHeaders,
Dictionary<string, HttpHeader> headers)
{ {
var nonUniqueResponseHeaders = headerCollection.NonUniqueHeaders;
var headers = headerCollection.Headers;
string tmpLine; string tmpLine;
while (!string.IsNullOrEmpty(tmpLine = await reader.ReadLineAsync())) while (!string.IsNullOrEmpty(tmpLine = await reader.ReadLineAsync()))
{ {
...@@ -29,7 +30,11 @@ namespace Titanium.Web.Proxy.Http ...@@ -29,7 +30,11 @@ namespace Titanium.Web.Proxy.Http
{ {
var existing = headers[newHeader.Name]; var existing = headers[newHeader.Name];
var nonUniqueHeaders = new List<HttpHeader> { existing, newHeader }; var nonUniqueHeaders = new List<HttpHeader>
{
existing,
newHeader
};
nonUniqueResponseHeaders.Add(newHeader.Name, nonUniqueHeaders); nonUniqueResponseHeaders.Add(newHeader.Name, nonUniqueHeaders);
headers.Remove(newHeader.Name); headers.Remove(newHeader.Name);
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
...@@ -27,7 +28,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -27,7 +28,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary> /// <summary>
/// Headers passed with Connect. /// Headers passed with Connect.
/// </summary> /// </summary>
public List<HttpHeader> ConnectHeaders { get; set; } public ConnectRequest ConnectRequest { get; set; }
/// <summary> /// <summary>
/// Web Request. /// Web Request.
...@@ -48,8 +49,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -48,8 +49,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary> /// <summary>
/// Is Https? /// Is Https?
/// </summary> /// </summary>
public bool IsHttps => Request.RequestUri.Scheme == Uri.UriSchemeHttps; public bool IsHttps => Request.IsHttps;
internal HttpWebClient() internal HttpWebClient()
{ {
...@@ -94,31 +94,17 @@ namespace Titanium.Web.Proxy.Http ...@@ -94,31 +94,17 @@ namespace Titanium.Web.Proxy.Http
$"{ServerConnection.UpStreamHttpProxy.UserName}:{ServerConnection.UpStreamHttpProxy.Password}"))); $"{ServerConnection.UpStreamHttpProxy.UserName}:{ServerConnection.UpStreamHttpProxy.Password}")));
} }
//write request headers //write request headers
foreach (var headerItem in Request.RequestHeaders) foreach (var header in Request.RequestHeaders)
{ {
var header = headerItem.Value; if (header.Name != "Proxy-Authorization")
if (headerItem.Key != "Proxy-Authorization")
{ {
requestLines.AppendLine($"{header.Name}: {header.Value}"); requestLines.AppendLine($"{header.Name}: {header.Value}");
} }
} }
//write non unique request headers
foreach (var headerItem in Request.NonUniqueRequestHeaders)
{
var headers = headerItem.Value;
foreach (var header in headers)
{
if (headerItem.Key != "Proxy-Authorization")
{
requestLines.AppendLine($"{header.Name}: {header.Value}");
}
}
}
requestLines.AppendLine(); requestLines.AppendLine();
var request = requestLines.ToString(); string request = requestLines.ToString();
var requestBytes = Encoding.ASCII.GetBytes(request); var requestBytes = Encoding.ASCII.GetBytes(request);
await stream.WriteAsync(requestBytes, 0, requestBytes.Length); await stream.WriteAsync(requestBytes, 0, requestBytes.Length);
...@@ -128,18 +114,21 @@ namespace Titanium.Web.Proxy.Http ...@@ -128,18 +114,21 @@ namespace Titanium.Web.Proxy.Http
{ {
if (Request.ExpectContinue) if (Request.ExpectContinue)
{ {
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3); string httpStatus = await ServerConnection.StreamReader.ReadLineAsync();
var responseStatusCode = httpResult[1].Trim();
var responseStatusDescription = httpResult[2].Trim(); Version version;
int responseStatusCode;
string responseStatusDescription;
Response.ParseResponseLine(httpStatus, out version, out responseStatusCode, out responseStatusDescription);
//find if server is willing for expect continue //find if server is willing for expect continue
if (responseStatusCode.Equals("100") if (responseStatusCode == (int)HttpStatusCode.Continue
&& responseStatusDescription.Equals("continue", StringComparison.CurrentCultureIgnoreCase)) && responseStatusDescription.Equals("continue", StringComparison.CurrentCultureIgnoreCase))
{ {
Request.Is100Continue = true; Request.Is100Continue = true;
await ServerConnection.StreamReader.ReadLineAsync(); await ServerConnection.StreamReader.ReadLineAsync();
} }
else if (responseStatusCode.Equals("417") else if (responseStatusCode == (int)HttpStatusCode.ExpectationFailed
&& responseStatusDescription.Equals("expectation failed", StringComparison.CurrentCultureIgnoreCase)) && responseStatusDescription.Equals("expectation failed", StringComparison.CurrentCultureIgnoreCase))
{ {
Request.ExpectationFailed = true; Request.ExpectationFailed = true;
...@@ -156,53 +145,49 @@ namespace Titanium.Web.Proxy.Http ...@@ -156,53 +145,49 @@ namespace Titanium.Web.Proxy.Http
internal async Task ReceiveResponse() internal async Task ReceiveResponse()
{ {
//return if this is already read //return if this is already read
if (Response.ResponseStatusCode != null) return; if (Response.ResponseStatusCode != 0)
return;
string line = await ServerConnection.StreamReader.ReadLineAsync(); string httpStatus = await ServerConnection.StreamReader.ReadLineAsync();
if (line == null) if (httpStatus == null)
{ {
throw new IOException(); throw new IOException();
} }
var httpResult = line.Split(ProxyConstants.SpaceSplit, 3); if (httpStatus == string.Empty)
if (string.IsNullOrEmpty(httpResult[0]))
{ {
//Empty content in first-line, try again //Empty content in first-line, try again
httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3); httpStatus = await ServerConnection.StreamReader.ReadLineAsync();
} }
var httpVersion = httpResult[0]; Version version;
int statusCode;
var version = HttpHeader.Version11; string statusDescription;
if (string.Equals(httpVersion, "HTTP/1.0", StringComparison.OrdinalIgnoreCase)) Response.ParseResponseLine(httpStatus, out version, out statusCode, out statusDescription);
{
version = HttpHeader.Version10;
}
Response.HttpVersion = version; Response.HttpVersion = version;
Response.ResponseStatusCode = httpResult[1].Trim(); Response.ResponseStatusCode = statusCode;
Response.ResponseStatusDescription = httpResult[2].Trim(); Response.ResponseStatusDescription = statusDescription;
//For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request //For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request
if (Response.ResponseStatusCode.Equals("100") if (Response.ResponseStatusCode == (int)HttpStatusCode.Continue
&& Response.ResponseStatusDescription.Equals("continue", StringComparison.CurrentCultureIgnoreCase)) && Response.ResponseStatusDescription.Equals("continue", StringComparison.CurrentCultureIgnoreCase))
{ {
//Read the next line after 100-continue //Read the next line after 100-continue
Response.Is100Continue = true; Response.Is100Continue = true;
Response.ResponseStatusCode = null; Response.ResponseStatusCode = 0;
await ServerConnection.StreamReader.ReadLineAsync(); await ServerConnection.StreamReader.ReadLineAsync();
//now receive response //now receive response
await ReceiveResponse(); await ReceiveResponse();
return; return;
} }
if (Response.ResponseStatusCode.Equals("417") if (Response.ResponseStatusCode == (int)HttpStatusCode.ExpectationFailed
&& Response.ResponseStatusDescription.Equals("expectation failed", StringComparison.CurrentCultureIgnoreCase)) && Response.ResponseStatusDescription.Equals("expectation failed", StringComparison.CurrentCultureIgnoreCase))
{ {
//read next line after expectation failed response //read next line after expectation failed response
Response.ExpectationFailed = true; Response.ExpectationFailed = true;
Response.ResponseStatusCode = null; Response.ResponseStatusCode = 0;
await ServerConnection.StreamReader.ReadLineAsync(); await ServerConnection.StreamReader.ReadLineAsync();
//now receive response //now receive response
await ReceiveResponse(); await ReceiveResponse();
...@@ -210,7 +195,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -210,7 +195,7 @@ namespace Titanium.Web.Proxy.Http
} }
//Read the response headers in to unique and non-unique header collections //Read the response headers in to unique and non-unique header collections
await HeaderParser.ReadHeaders(ServerConnection.StreamReader, Response.NonUniqueResponseHeaders, Response.ResponseHeaders); await HeaderParser.ReadHeaders(ServerConnection.StreamReader, Response.ResponseHeaders);
} }
/// <summary> /// <summary>
...@@ -218,7 +203,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -218,7 +203,7 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
ConnectHeaders = null; ConnectRequest = null;
Request.Dispose(); Request.Dispose();
Response.Dispose(); Response.Dispose();
......
using System; using System;
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
...@@ -22,6 +23,16 @@ namespace Titanium.Web.Proxy.Http ...@@ -22,6 +23,16 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
public Uri RequestUri { get; set; } public Uri RequestUri { get; set; }
/// <summary>
/// Is Https?
/// </summary>
public bool IsHttps => RequestUri.Scheme == ProxyServer.UriSchemeHttps;
/// <summary>
/// The original request Url.
/// </summary>
public string OriginalRequestUrl { get; set; }
/// <summary> /// <summary>
/// Request Http Version /// Request Http Version
/// </summary> /// </summary>
...@@ -41,40 +52,18 @@ namespace Titanium.Web.Proxy.Http ...@@ -41,40 +52,18 @@ namespace Titanium.Web.Proxy.Http
{ {
get get
{ {
var hasHeader = RequestHeaders.ContainsKey("host"); return RequestHeaders.GetHeaderValueOrNull("host");
return hasHeader ? RequestHeaders["host"].Value : null;
} }
set set
{ {
var hasHeader = RequestHeaders.ContainsKey("host"); RequestHeaders.SetOrAddHeaderValue("host", value);
if (hasHeader)
{
RequestHeaders["host"].Value = value;
}
else
{
RequestHeaders.Add("Host", new HttpHeader("Host", value));
}
} }
} }
/// <summary> /// <summary>
/// Content encoding header value /// Content encoding header value
/// </summary> /// </summary>
public string ContentEncoding public string ContentEncoding => RequestHeaders.GetHeaderValueOrNull("content-encoding");
{
get
{
var hasHeader = RequestHeaders.ContainsKey("content-encoding");
if (hasHeader)
{
return RequestHeaders["content-encoding"].Value;
}
return null;
}
}
/// <summary> /// <summary>
/// Request content-length /// Request content-length
...@@ -83,17 +72,15 @@ namespace Titanium.Web.Proxy.Http ...@@ -83,17 +72,15 @@ namespace Titanium.Web.Proxy.Http
{ {
get get
{ {
var hasHeader = RequestHeaders.ContainsKey("content-length"); string headerValue = RequestHeaders.GetHeaderValueOrNull("content-length");
if (hasHeader == false) if (headerValue == null)
{ {
return -1; return -1;
} }
var header = RequestHeaders["content-length"];
long contentLen; long contentLen;
long.TryParse(header.Value, out contentLen); long.TryParse(headerValue, out contentLen);
if (contentLen >= 0) if (contentLen >= 0)
{ {
return contentLen; return contentLen;
...@@ -103,29 +90,14 @@ namespace Titanium.Web.Proxy.Http ...@@ -103,29 +90,14 @@ namespace Titanium.Web.Proxy.Http
} }
set set
{ {
var hasHeader = RequestHeaders.ContainsKey("content-length");
var header = RequestHeaders["content-length"];
if (value >= 0) if (value >= 0)
{ {
if (hasHeader) RequestHeaders.SetOrAddHeaderValue("content-length", value.ToString());
{
header.Value = value.ToString();
}
else
{
RequestHeaders.Add("content-length", new HttpHeader("content-length", value.ToString()));
}
IsChunked = false; IsChunked = false;
} }
else else
{ {
if (hasHeader) RequestHeaders.RemoveHeader("content-length");
{
RequestHeaders.Remove("content-length");
}
} }
} }
} }
...@@ -137,29 +109,11 @@ namespace Titanium.Web.Proxy.Http ...@@ -137,29 +109,11 @@ namespace Titanium.Web.Proxy.Http
{ {
get get
{ {
var hasHeader = RequestHeaders.ContainsKey("content-type"); return RequestHeaders.GetHeaderValueOrNull("content-type");
if (hasHeader)
{
var header = RequestHeaders["content-type"];
return header.Value;
}
return null;
} }
set set
{ {
var hasHeader = RequestHeaders.ContainsKey("content-type"); RequestHeaders.SetOrAddHeaderValue("content-type", value);
if (hasHeader)
{
var header = RequestHeaders["content-type"];
header.Value = value;
}
else
{
RequestHeaders.Add("content-type", new HttpHeader("content-type", value));
}
} }
} }
...@@ -170,41 +124,19 @@ namespace Titanium.Web.Proxy.Http ...@@ -170,41 +124,19 @@ namespace Titanium.Web.Proxy.Http
{ {
get get
{ {
var hasHeader = RequestHeaders.ContainsKey("transfer-encoding"); string headerValue = RequestHeaders.GetHeaderValueOrNull("transfer-encoding");
return headerValue != null && headerValue.ContainsIgnoreCase("chunked");
if (hasHeader)
{
var header = RequestHeaders["transfer-encoding"];
return header.Value.ContainsIgnoreCase("chunked");
}
return false;
} }
set set
{ {
var hasHeader = RequestHeaders.ContainsKey("transfer-encoding");
if (value) if (value)
{ {
if (hasHeader) RequestHeaders.SetOrAddHeaderValue("transfer-encoding", "chunked");
{
var header = RequestHeaders["transfer-encoding"];
header.Value = "chunked";
}
else
{
RequestHeaders.Add("transfer-encoding", new HttpHeader("transfer-encoding", "chunked"));
}
ContentLength = -1; ContentLength = -1;
} }
else else
{ {
if (hasHeader) RequestHeaders.RemoveHeader("transfer-encoding");
{
RequestHeaders.Remove("transfer-encoding");
}
} }
} }
} }
...@@ -216,12 +148,8 @@ namespace Titanium.Web.Proxy.Http ...@@ -216,12 +148,8 @@ namespace Titanium.Web.Proxy.Http
{ {
get get
{ {
var hasHeader = RequestHeaders.ContainsKey("expect"); string headerValue = RequestHeaders.GetHeaderValueOrNull("expect");
return headerValue != null && headerValue.Equals("100-continue");
if (!hasHeader) return false;
var header = RequestHeaders["expect"];
return header.Value.Equals("100-continue");
} }
} }
...@@ -246,7 +174,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -246,7 +174,7 @@ namespace Titanium.Web.Proxy.Http
internal byte[] RequestBody { get; set; } internal byte[] RequestBody { get; set; }
/// <summary> /// <summary>
/// request body as string /// Request body as string
/// </summary> /// </summary>
internal string RequestBodyString { get; set; } internal string RequestBodyString { get; set; }
...@@ -267,28 +195,21 @@ namespace Titanium.Web.Proxy.Http ...@@ -267,28 +195,21 @@ namespace Titanium.Web.Proxy.Http
{ {
get get
{ {
var hasHeader = RequestHeaders.ContainsKey("upgrade"); string headerValue = RequestHeaders.GetHeaderValueOrNull("upgrade");
if (hasHeader == false) if (headerValue == null)
{ {
return false; return false;
} }
var header = RequestHeaders["upgrade"]; return headerValue.Equals("websocket", StringComparison.CurrentCultureIgnoreCase);
return header.Value.Equals("websocket", StringComparison.CurrentCultureIgnoreCase);
} }
} }
/// <summary> /// <summary>
/// Unique Request header collection /// Request header collection
/// </summary> /// </summary>
public Dictionary<string, HttpHeader> RequestHeaders { get; set; } public HeaderCollection RequestHeaders { get; private set; } = new HeaderCollection();
/// <summary>
/// Non Unique headers
/// </summary>
public Dictionary<string, List<HttpHeader>> NonUniqueRequestHeaders { get; set; }
/// <summary> /// <summary>
/// Does server responsed positively for 100 continue request /// Does server responsed positively for 100 continue request
...@@ -301,151 +222,82 @@ namespace Titanium.Web.Proxy.Http ...@@ -301,151 +222,82 @@ namespace Titanium.Web.Proxy.Http
public bool ExpectationFailed { get; internal set; } public bool ExpectationFailed { get; internal set; }
/// <summary> /// <summary>
/// Constructor. /// Gets the header text.
/// </summary> /// </summary>
public Request() public string HeaderText
{ {
RequestHeaders = new Dictionary<string, HttpHeader>(StringComparer.OrdinalIgnoreCase); get
NonUniqueRequestHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// True if header exists
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public bool HeaderExists(string name)
{ {
if (RequestHeaders.ContainsKey(name) var sb = new StringBuilder();
|| NonUniqueRequestHeaders.ContainsKey(name)) sb.AppendLine($"{Method} {OriginalRequestUrl} HTTP/{HttpVersion.Major}.{HttpVersion.Minor}");
foreach (var header in RequestHeaders)
{ {
return true; sb.AppendLine(header.ToString());
} }
return false; sb.AppendLine();
return sb.ToString();
} }
/// <summary>
/// Returns all headers with given name if exists
/// Returns null if does'nt exist
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public List<HttpHeader> GetHeaders(string name)
{
if (RequestHeaders.ContainsKey(name))
{
return new List<HttpHeader>() { RequestHeaders[name] };
} }
else if (NonUniqueRequestHeaders.ContainsKey(name))
internal static void ParseRequestLine(string httpCmd, out string httpMethod, out string httpUrl, out Version version)
{ {
return new List<HttpHeader>(NonUniqueRequestHeaders[name]); //break up the line into three components (method, remote URL & Http Version)
} var httpCmdSplit = httpCmd.Split(ProxyConstants.SpaceSplit, 3);
return null; if (httpCmdSplit.Length < 2)
{
throw new Exception("Invalid HTTP request line: " + httpCmd);
} }
/// <summary> //Find the request Verb
/// Returns all headers httpMethod = httpCmdSplit[0];
/// </summary> if (!IsAllUpper(httpMethod))
/// <returns></returns>
public List<HttpHeader> GetAllHeaders()
{ {
var result = new List<HttpHeader>(); //method should be upper cased: https://tools.ietf.org/html/rfc7231#section-4
result.AddRange(RequestHeaders.Select(x => x.Value)); //todo: create protocol violation message
result.AddRange(NonUniqueRequestHeaders.SelectMany(x => x.Value));
return result; //fix it
httpMethod = httpMethod.ToUpper();
} }
/// <summary> httpUrl = httpCmdSplit[1];
/// Add a new header with given name and value
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
public void AddHeader(string name, string value)
{
AddHeader(new HttpHeader(name, value));
}
/// <summary> //parse the HTTP version
/// Adds the given header object to Request version = HttpHeader.Version11;
/// </summary> if (httpCmdSplit.Length == 3)
/// <param name="newHeader"></param>
public void AddHeader(HttpHeader newHeader)
{ {
if (NonUniqueRequestHeaders.ContainsKey(newHeader.Name)) string httpVersion = httpCmdSplit[2].Trim();
{
NonUniqueRequestHeaders[newHeader.Name].Add(newHeader);
return;
}
if (RequestHeaders.ContainsKey(newHeader.Name)) if (string.Equals(httpVersion, "HTTP/1.0", StringComparison.OrdinalIgnoreCase))
{ {
var existing = RequestHeaders[newHeader.Name]; version = HttpHeader.Version10;
RequestHeaders.Remove(newHeader.Name);
NonUniqueRequestHeaders.Add(newHeader.Name,
new List<HttpHeader>() { existing, newHeader });
} }
else
{
RequestHeaders.Add(newHeader.Name, newHeader);
} }
} }
/// <summary> private static bool IsAllUpper(string input)
/// removes all headers with given name
/// </summary>
/// <param name="headerName"></param>
/// <returns>True if header was removed
/// False if no header exists with given name</returns>
public bool RemoveHeader(string headerName)
{ {
if (RequestHeaders.ContainsKey(headerName)) for (int i = 0; i < input.Length; i++)
{ {
RequestHeaders.Remove(headerName); char ch = input[i];
return true; if (ch < 'A' || ch > 'Z')
}
else if (NonUniqueRequestHeaders.ContainsKey(headerName))
{ {
NonUniqueRequestHeaders.Remove(headerName);
return true;
}
return false; return false;
} }
/// <summary>
/// Removes given header object if it exist
/// </summary>
/// <param name="header">Returns true if header exists and was removed </param>
public bool RemoveHeader(HttpHeader header)
{
if (RequestHeaders.ContainsKey(header.Name))
{
if (RequestHeaders[header.Name].Equals(header))
{
RequestHeaders.Remove(header.Name);
return true;
} }
}
else if (NonUniqueRequestHeaders.ContainsKey(header.Name))
{
if (NonUniqueRequestHeaders[header.Name]
.RemoveAll(x => x.Equals(header)) > 0)
{
return true; return true;
} }
}
return false; /// <summary>
/// Constructor.
/// </summary>
public Request()
{
} }
/// <summary> /// <summary>
/// Dispose off /// Dispose off
/// </summary> /// </summary>
...@@ -455,12 +307,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -455,12 +307,9 @@ namespace Titanium.Web.Proxy.Http
//but just to be on safe side //but just to be on safe side
RequestHeaders = null; RequestHeaders = null;
NonUniqueRequestHeaders = null;
RequestBody = null; RequestBody = null;
RequestBody = null; RequestBodyString = null;
} }
} }
} }
using System; using System;
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.Linq;
using System.Text; using System.Text;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
...@@ -16,7 +16,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -16,7 +16,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary> /// <summary>
/// Response Status Code. /// Response Status Code.
/// </summary> /// </summary>
public string ResponseStatusCode { get; set; } public int ResponseStatusCode { get; set; }
/// <summary> /// <summary>
/// Response Status description. /// Response Status description.
...@@ -31,18 +31,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -31,18 +31,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary> /// <summary>
/// Content encoding for this response /// Content encoding for this response
/// </summary> /// </summary>
public string ContentEncoding public string ContentEncoding => ResponseHeaders.GetHeaderValueOrNull("content-encoding")?.Trim();
{
get
{
var hasHeader = ResponseHeaders.ContainsKey("content-encoding");
if (!hasHeader) return null;
var header = ResponseHeaders["content-encoding"];
return header.Value.Trim();
}
}
/// <summary> /// <summary>
/// Http version /// Http version
...@@ -50,48 +39,56 @@ namespace Titanium.Web.Proxy.Http ...@@ -50,48 +39,56 @@ namespace Titanium.Web.Proxy.Http
public Version HttpVersion { get; set; } public Version HttpVersion { get; set; }
/// <summary> /// <summary>
/// Keep the connection alive? /// Has response body?
/// </summary> /// </summary>
public bool ResponseKeepAlive public bool HasBody
{ {
get get
{ {
var hasHeader = ResponseHeaders.ContainsKey("connection"); //Has body only if response is chunked or content length >0
//If none are true then check if connection:close header exist, if so write response until server or client terminates the connection
if (hasHeader) if (IsChunked || ContentLength > 0 || !ResponseKeepAlive)
{ {
var header = ResponseHeaders["connection"]; return true;
}
if (header.Value.ContainsIgnoreCase("close")) //has response if connection:keep-alive header exist and when version is http/1.0
//Because in Http 1.0 server can return a response without content-length (expectation being client would read until end of stream)
if (ResponseKeepAlive && HttpVersion.Minor == 0)
{ {
return false; return true;
}
} }
return true; return false;
} }
} }
/// <summary> /// <summary>
/// Content type of this response /// Keep the connection alive?
/// </summary> /// </summary>
public string ContentType public bool ResponseKeepAlive
{ {
get get
{ {
var hasHeader = ResponseHeaders.ContainsKey("content-type"); string headerValue = ResponseHeaders.GetHeaderValueOrNull("connection");
if (hasHeader) if (headerValue != null)
{ {
var header = ResponseHeaders["content-type"]; if (headerValue.ContainsIgnoreCase("close"))
{
return header.Value; return false;
}
} }
return null; return true;
} }
} }
/// <summary>
/// Content type of this response
/// </summary>
public string ContentType => ResponseHeaders.GetHeaderValueOrNull("content-type");
/// <summary> /// <summary>
/// Length of response body /// Length of response body
/// </summary> /// </summary>
...@@ -99,17 +96,15 @@ namespace Titanium.Web.Proxy.Http ...@@ -99,17 +96,15 @@ namespace Titanium.Web.Proxy.Http
{ {
get get
{ {
var hasHeader = ResponseHeaders.ContainsKey("content-length"); string headerValue = ResponseHeaders.GetHeaderValueOrNull("content-length");
if (hasHeader == false) if (headerValue == null)
{ {
return -1; return -1;
} }
var header = ResponseHeaders["content-length"];
long contentLen; long contentLen;
long.TryParse(header.Value, out contentLen); long.TryParse(headerValue, out contentLen);
if (contentLen >= 0) if (contentLen >= 0)
{ {
return contentLen; return contentLen;
...@@ -119,28 +114,14 @@ namespace Titanium.Web.Proxy.Http ...@@ -119,28 +114,14 @@ namespace Titanium.Web.Proxy.Http
} }
set set
{ {
var hasHeader = ResponseHeaders.ContainsKey("content-length");
if (value >= 0) if (value >= 0)
{ {
if (hasHeader) ResponseHeaders.SetOrAddHeaderValue("content-length", value.ToString());
{
var header = ResponseHeaders["content-length"];
header.Value = value.ToString();
}
else
{
ResponseHeaders.Add("content-length", new HttpHeader("content-length", value.ToString()));
}
IsChunked = false; IsChunked = false;
} }
else else
{ {
if (hasHeader) ResponseHeaders.RemoveHeader("content-length");
{
ResponseHeaders.Remove("content-length");
}
} }
} }
} }
...@@ -152,44 +133,19 @@ namespace Titanium.Web.Proxy.Http ...@@ -152,44 +133,19 @@ namespace Titanium.Web.Proxy.Http
{ {
get get
{ {
var hasHeader = ResponseHeaders.ContainsKey("transfer-encoding"); string headerValue = ResponseHeaders.GetHeaderValueOrNull("transfer-encoding");
return headerValue != null && headerValue.ContainsIgnoreCase("chunked");
if (hasHeader)
{
var header = ResponseHeaders["transfer-encoding"];
if (header.Value.ContainsIgnoreCase("chunked"))
{
return true;
}
}
return false;
} }
set set
{ {
var hasHeader = ResponseHeaders.ContainsKey("transfer-encoding");
if (value) if (value)
{ {
if (hasHeader) ResponseHeaders.SetOrAddHeaderValue("transfer-encoding", "chunked");
{
var header = ResponseHeaders["transfer-encoding"];
header.Value = "chunked";
}
else
{
ResponseHeaders.Add("transfer-encoding", new HttpHeader("transfer-encoding", "chunked"));
}
ContentLength = -1; ContentLength = -1;
} }
else else
{ {
if (hasHeader) ResponseHeaders.RemoveHeader("transfer-encoding");
{
ResponseHeaders.Remove("transfer-encoding");
}
} }
} }
} }
...@@ -197,20 +153,15 @@ namespace Titanium.Web.Proxy.Http ...@@ -197,20 +153,15 @@ namespace Titanium.Web.Proxy.Http
/// <summary> /// <summary>
/// Collection of all response headers /// Collection of all response headers
/// </summary> /// </summary>
public Dictionary<string, HttpHeader> ResponseHeaders { get; set; } public HeaderCollection ResponseHeaders { get; private set; } = new HeaderCollection();
/// <summary> /// <summary>
/// Non Unique headers /// Response body content as byte array
/// </summary>
public Dictionary<string, List<HttpHeader>> NonUniqueResponseHeaders { get; set; }
/// <summary>
/// response body content as byte array
/// </summary> /// </summary>
internal byte[] ResponseBody { get; set; } internal byte[] ResponseBody { get; set; }
/// <summary> /// <summary>
/// response body as string /// Response body as string
/// </summary> /// </summary>
internal string ResponseBodyString { get; set; } internal string ResponseBodyString { get; set; }
...@@ -235,147 +186,54 @@ namespace Titanium.Web.Proxy.Http ...@@ -235,147 +186,54 @@ namespace Titanium.Web.Proxy.Http
public bool ExpectationFailed { get; internal set; } public bool ExpectationFailed { get; internal set; }
/// <summary> /// <summary>
/// Constructor. /// Gets the resposne status.
/// </summary>
public Response()
{
ResponseHeaders = new Dictionary<string, HttpHeader>(StringComparer.OrdinalIgnoreCase);
NonUniqueResponseHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// True if header exists
/// </summary> /// </summary>
/// <param name="name"></param> public string ResponseStatus => $"HTTP/{HttpVersion?.Major}.{HttpVersion?.Minor} {ResponseStatusCode} {ResponseStatusDescription}";
/// <returns></returns>
public bool HeaderExists(string name)
{
if(ResponseHeaders.ContainsKey(name)
|| NonUniqueResponseHeaders.ContainsKey(name))
{
return true;
}
return false;
}
/// <summary> /// <summary>
/// Returns all headers with given name if exists /// Gets the header text.
/// Returns null if does'nt exist
/// </summary> /// </summary>
/// <param name="name"></param> public string HeaderText
/// <returns></returns>
public List<HttpHeader> GetHeaders(string name)
{ {
if (ResponseHeaders.ContainsKey(name)) get
{ {
return new List<HttpHeader>() { ResponseHeaders[name] }; var sb = new StringBuilder();
} sb.AppendLine(ResponseStatus);
else if (NonUniqueResponseHeaders.ContainsKey(name)) foreach (var header in ResponseHeaders)
{ {
return new List<HttpHeader>(NonUniqueResponseHeaders[name]); sb.AppendLine(header.ToString());
} }
return null; sb.AppendLine();
return sb.ToString();
} }
/// <summary>
/// Returns all headers
/// </summary>
/// <returns></returns>
public List<HttpHeader> GetAllHeaders()
{
var result = new List<HttpHeader>();
result.AddRange(ResponseHeaders.Select(x => x.Value));
result.AddRange(NonUniqueResponseHeaders.SelectMany(x => x.Value));
return result;
} }
/// <summary> internal static void ParseResponseLine(string httpStatus, out Version version, out int statusCode, out string statusDescription)
/// Add a new header with given name and value
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
public void AddHeader(string name, string value)
{
AddHeader(new HttpHeader(name, value));
}
/// <summary>
/// Adds the given header object to Response
/// </summary>
/// <param name="newHeader"></param>
public void AddHeader(HttpHeader newHeader)
{ {
if (NonUniqueResponseHeaders.ContainsKey(newHeader.Name)) var httpResult = httpStatus.Split(ProxyConstants.SpaceSplit, 3);
if (httpResult.Length != 3)
{ {
NonUniqueResponseHeaders[newHeader.Name].Add(newHeader); throw new Exception("Invalid HTTP status line: " + httpStatus);
return;
} }
if (ResponseHeaders.ContainsKey(newHeader.Name)) string httpVersion = httpResult[0];
{
var existing = ResponseHeaders[newHeader.Name];
ResponseHeaders.Remove(newHeader.Name);
NonUniqueResponseHeaders.Add(newHeader.Name, version = HttpHeader.Version11;
new List<HttpHeader>() { existing, newHeader }); if (string.Equals(httpVersion, "HTTP/1.0", StringComparison.OrdinalIgnoreCase))
}
else
{ {
ResponseHeaders.Add(newHeader.Name, newHeader); version = HttpHeader.Version10;
}
} }
/// <summary> statusCode = int.Parse(httpResult[1]);
/// removes all headers with given name statusDescription = httpResult[2];
/// </summary>
/// <param name="headerName"></param>
/// <returns>True if header was removed
/// False if no header exists with given name</returns>
public bool RemoveHeader(string headerName)
{
if(ResponseHeaders.ContainsKey(headerName))
{
ResponseHeaders.Remove(headerName);
return true;
}
else if (NonUniqueResponseHeaders.ContainsKey(headerName))
{
NonUniqueResponseHeaders.Remove(headerName);
return true;
}
return false;
} }
/// <summary> /// <summary>
/// Removes given header object if it exist /// Constructor.
/// </summary> /// </summary>
/// <param name="header">Returns true if header exists and was removed </param> public Response()
public bool RemoveHeader(HttpHeader header)
{
if (ResponseHeaders.ContainsKey(header.Name))
{
if (ResponseHeaders[header.Name].Equals(header))
{
ResponseHeaders.Remove(header.Name);
return true;
}
}
else if (NonUniqueResponseHeaders.ContainsKey(header.Name))
{
if (NonUniqueResponseHeaders[header.Name]
.RemoveAll(x => x.Equals(header)) > 0)
{ {
return true;
}
}
return false;
} }
/// <summary> /// <summary>
...@@ -387,7 +245,6 @@ namespace Titanium.Web.Proxy.Http ...@@ -387,7 +245,6 @@ namespace Titanium.Web.Proxy.Http
//but just to be on safe side //but just to be on safe side
ResponseHeaders = null; ResponseHeaders = null;
NonUniqueResponseHeaders = null;
ResponseBody = null; ResponseBody = null;
ResponseBodyString = null; ResponseBodyString = null;
......
...@@ -13,7 +13,10 @@ namespace Titanium.Web.Proxy.Http.Responses ...@@ -13,7 +13,10 @@ namespace Titanium.Web.Proxy.Http.Responses
/// <param name="status"></param> /// <param name="status"></param>
public GenericResponse(HttpStatusCode status) public GenericResponse(HttpStatusCode status)
{ {
ResponseStatusCode = ((int)status).ToString(); ResponseStatusCode = (int)status;
//todo: this is not really correct, status description should contain spaces, too
//see: https://tools.ietf.org/html/rfc7231#section-6.1
ResponseStatusDescription = status.ToString(); ResponseStatusDescription = status.ToString();
} }
...@@ -22,7 +25,7 @@ namespace Titanium.Web.Proxy.Http.Responses ...@@ -22,7 +25,7 @@ namespace Titanium.Web.Proxy.Http.Responses
/// </summary> /// </summary>
/// <param name="statusCode"></param> /// <param name="statusCode"></param>
/// <param name="statusDescription"></param> /// <param name="statusDescription"></param>
public GenericResponse(string statusCode, string statusDescription) public GenericResponse(int statusCode, string statusDescription)
{ {
ResponseStatusCode = statusCode; ResponseStatusCode = statusCode;
ResponseStatusDescription = statusDescription; ResponseStatusDescription = statusDescription;
......
namespace Titanium.Web.Proxy.Http.Responses using System.Net;
namespace Titanium.Web.Proxy.Http.Responses
{ {
/// <summary> /// <summary>
/// 200 Ok response /// 200 Ok response
...@@ -10,8 +12,8 @@ ...@@ -10,8 +12,8 @@
/// </summary> /// </summary>
public OkResponse() public OkResponse()
{ {
ResponseStatusCode = "200"; ResponseStatusCode = (int)HttpStatusCode.OK;
ResponseStatusDescription = "Ok"; ResponseStatusDescription = "OK";
} }
} }
} }
namespace Titanium.Web.Proxy.Http.Responses using System.Net;
namespace Titanium.Web.Proxy.Http.Responses
{ {
/// <summary> /// <summary>
/// Redirect response /// Redirect response
...@@ -10,7 +12,7 @@ ...@@ -10,7 +12,7 @@
/// </summary> /// </summary>
public RedirectResponse() public RedirectResponse()
{ {
ResponseStatusCode = "302"; ResponseStatusCode = (int)HttpStatusCode.Found;
ResponseStatusDescription = "Found"; ResponseStatusDescription = "Found";
} }
} }
......
...@@ -69,12 +69,6 @@ namespace Titanium.Web.Proxy.Models ...@@ -69,12 +69,6 @@ namespace Titanium.Web.Proxy.Models
internal bool IsSystemHttpsProxy { get; set; } internal bool IsSystemHttpsProxy { get; set; }
/// <summary>
/// Remote HTTPS ports we are allowed to communicate with
/// CONNECT request to ports other than these will not be decrypted
/// </summary>
public List<int> RemoteHttpsPorts { get; set; }
/// <summary> /// <summary>
/// List of host names to exclude using Regular Expressions. /// List of host names to exclude using Regular Expressions.
/// </summary> /// </summary>
...@@ -120,11 +114,8 @@ namespace Titanium.Web.Proxy.Models ...@@ -120,11 +114,8 @@ namespace Titanium.Web.Proxy.Models
/// <param name="ipAddress"></param> /// <param name="ipAddress"></param>
/// <param name="port"></param> /// <param name="port"></param>
/// <param name="enableSsl"></param> /// <param name="enableSsl"></param>
public ExplicitProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl) public ExplicitProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl) : base(ipAddress, port, enableSsl)
: base(ipAddress, port, enableSsl)
{ {
//init to well known HTTPS ports
RemoteHttpsPorts = new List<int> { 443, 8443 };
} }
} }
...@@ -146,8 +137,7 @@ namespace Titanium.Web.Proxy.Models ...@@ -146,8 +137,7 @@ namespace Titanium.Web.Proxy.Models
/// <param name="ipAddress"></param> /// <param name="ipAddress"></param>
/// <param name="port"></param> /// <param name="port"></param>
/// <param name="enableSsl"></param> /// <param name="enableSsl"></param>
public TransparentProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl) public TransparentProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl) : base(ipAddress, port, enableSsl)
: base(ipAddress, port, enableSsl)
{ {
GenericCertificateName = "localhost"; GenericCertificateName = "localhost";
} }
......
...@@ -9,9 +9,9 @@ namespace Titanium.Web.Proxy.Models ...@@ -9,9 +9,9 @@ namespace Titanium.Web.Proxy.Models
/// </summary> /// </summary>
public class HttpHeader public class HttpHeader
{ {
internal static Version Version10 = new Version(1, 0); internal static readonly Version Version10 = new Version(1, 0);
internal static Version Version11 = new Version(1, 1); internal static readonly Version Version11 = new Version(1, 1);
/// <summary> /// <summary>
/// Constructor. /// Constructor.
......
#if !NET45
using System;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Operators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.X509;
using System.Security.Cryptography;
using System.IO;
namespace Titanium.Web.Proxy.Network.Certificate
{
/// <summary>
/// Implements certificate generation operations.
/// </summary>
internal class BCCertificateMaker : ICertificateMaker
{
private const int certificateValidDays = 1825;
private const int certificateGraceDays = 366;
/// <summary>
/// Makes the certificate.
/// </summary>
/// <param name="sSubjectCn">The s subject cn.</param>
/// <param name="isRoot">if set to <c>true</c> [is root].</param>
/// <param name="signingCert">The signing cert.</param>
/// <returns>X509Certificate2 instance.</returns>
public X509Certificate2 MakeCertificate(string sSubjectCn, bool isRoot, X509Certificate2 signingCert = null)
{
return MakeCertificateInternal(sSubjectCn, isRoot, true, signingCert);
}
/// <summary>
/// Generates the certificate.
/// </summary>
/// <param name="subjectName">Name of the subject.</param>
/// <param name="issuerName">Name of the issuer.</param>
/// <param name="validFrom">The valid from.</param>
/// <param name="validTo">The valid to.</param>
/// <param name="keyStrength">The key strength.</param>
/// <param name="signatureAlgorithm">The signature algorithm.</param>
/// <param name="issuerPrivateKey">The issuer private key.</param>
/// <param name="hostName">The host name</param>
/// <returns>X509Certificate2 instance.</returns>
/// <exception cref="PemException">Malformed sequence in RSA private key</exception>
private static X509Certificate2 GenerateCertificate(string hostName,
string subjectName,
string issuerName, DateTime validFrom,
DateTime validTo, int keyStrength = 2048,
string signatureAlgorithm = "SHA256WithRSA",
AsymmetricKeyParameter issuerPrivateKey = null)
{
// Generating Random Numbers
var randomGenerator = new CryptoApiRandomGenerator();
var secureRandom = new SecureRandom(randomGenerator);
// The Certificate Generator
var certificateGenerator = new X509V3CertificateGenerator();
// Serial Number
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), secureRandom);
certificateGenerator.SetSerialNumber(serialNumber);
// Issuer and Subject Name
var subjectDn = new X509Name(subjectName);
var issuerDn = new X509Name(issuerName);
certificateGenerator.SetIssuerDN(issuerDn);
certificateGenerator.SetSubjectDN(subjectDn);
certificateGenerator.SetNotBefore(validFrom);
certificateGenerator.SetNotAfter(validTo);
if (hostName != null)
{
//add subject alternative names
var subjectAlternativeNames = new Asn1Encodable[] { new GeneralName(GeneralName.DnsName, hostName), };
var subjectAlternativeNamesExtension = new DerSequence(subjectAlternativeNames);
certificateGenerator.AddExtension(X509Extensions.SubjectAlternativeName.Id, false, subjectAlternativeNamesExtension);
}
// Subject Public Key
var keyGenerationParameters = new KeyGenerationParameters(secureRandom, keyStrength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
// Set certificate intended purposes to only Server Authentication
certificateGenerator.AddExtension(X509Extensions.ExtendedKeyUsage.Id, false, new ExtendedKeyUsage(KeyPurposeID.IdKPServerAuth));
var signatureFactory = new Asn1SignatureFactory(signatureAlgorithm, issuerPrivateKey ?? subjectKeyPair.Private, secureRandom);
// Self-sign the certificate
var certificate = certificateGenerator.Generate(signatureFactory);
// Corresponding private key
var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(subjectKeyPair.Private);
var seq = (Asn1Sequence)Asn1Object.FromByteArray(privateKeyInfo.ParsePrivateKey().GetDerEncoded());
if (seq.Count != 9)
{
throw new PemException("Malformed sequence in RSA private key");
}
var rsa = RsaPrivateKeyStructure.GetInstance(seq);
var rsaparams = new RsaPrivateCrtKeyParameters(rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent, rsa.Prime1, rsa.Prime2, rsa.Exponent1,
rsa.Exponent2, rsa.Coefficient);
var x509Certificate = WithPrivateKey(certificate, rsaparams);
x509Certificate.FriendlyName = subjectName;
return x509Certificate;
}
private static X509Certificate2 WithPrivateKey(Org.BouncyCastle.X509.X509Certificate certificate, AsymmetricKeyParameter privateKey)
{
const string password = "password";
var store = new Pkcs12Store();
var entry = new X509CertificateEntry(certificate);
store.SetCertificateEntry(certificate.SubjectDN.ToString(), entry);
store.SetKeyEntry(certificate.SubjectDN.ToString(), new AsymmetricKeyEntry(privateKey), new[] { entry });
using (var ms = new MemoryStream())
{
store.Save(ms, password.ToCharArray(), new SecureRandom(new CryptoApiRandomGenerator()));
return new X509Certificate2(ms.ToArray(), password, X509KeyStorageFlags.Exportable);
}
}
/// <summary>
/// Makes the certificate internal.
/// </summary>
/// <param name="isRoot">if set to <c>true</c> [is root].</param>
/// <param name="hostName">hostname for certificate</param>
/// <param name="subjectName">The full subject.</param>
/// <param name="validFrom">The valid from.</param>
/// <param name="validTo">The valid to.</param>
/// <param name="signingCertificate">The signing certificate.</param>
/// <returns>X509Certificate2 instance.</returns>
/// <exception cref="System.ArgumentException">You must specify a Signing Certificate if and only if you are not creating a root.</exception>
private X509Certificate2 MakeCertificateInternal(bool isRoot,
string hostName, string subjectName,
DateTime validFrom, DateTime validTo, X509Certificate2 signingCertificate)
{
if (isRoot != (null == signingCertificate))
{
throw new ArgumentException("You must specify a Signing Certificate if and only if you are not creating a root.", nameof(signingCertificate));
}
if (isRoot)
{
return GenerateCertificate(null, subjectName, subjectName, validFrom, validTo);
}
else
{
var rsa = signingCertificate.GetRSAPrivateKey();
var kp = GetRsaKeyPair(rsa.ExportParameters(true));
return GenerateCertificate(hostName, subjectName, signingCertificate.Subject, validFrom, validTo, issuerPrivateKey: kp.Private);
}
}
static AsymmetricCipherKeyPair GetRsaKeyPair(RSAParameters rp)
{
BigInteger modulus = new BigInteger(1, rp.Modulus);
BigInteger pubExp = new BigInteger(1, rp.Exponent);
RsaKeyParameters pubKey = new RsaKeyParameters(
false,
modulus,
pubExp);
RsaPrivateCrtKeyParameters privKey = new RsaPrivateCrtKeyParameters(
modulus,
pubExp,
new BigInteger(1, rp.D),
new BigInteger(1, rp.P),
new BigInteger(1, rp.Q),
new BigInteger(1, rp.DP),
new BigInteger(1, rp.DQ),
new BigInteger(1, rp.InverseQ));
return new AsymmetricCipherKeyPair(pubKey, privKey);
}
/// <summary>
/// Makes the certificate internal.
/// </summary>
/// <param name="subject">The s subject cn.</param>
/// <param name="isRoot">if set to <c>true</c> [is root].</param>
/// <param name="switchToMtaIfNeeded">if set to <c>true</c> [switch to MTA if needed].</param>
/// <param name="signingCert">The signing cert.</param>
/// <param name="cancellationToken">Task cancellation token</param>
/// <returns>X509Certificate2.</returns>
private X509Certificate2 MakeCertificateInternal(string subject, bool isRoot,
bool switchToMtaIfNeeded, X509Certificate2 signingCert = null,
CancellationToken cancellationToken = default(CancellationToken))
{
return MakeCertificateInternal(isRoot, subject, $"CN={subject}", DateTime.UtcNow.AddDays(-certificateGraceDays), DateTime.UtcNow.AddDays(certificateValidDays), isRoot ? null : signingCert);
}
class CryptoApiRandomGenerator : IRandomGenerator
{
readonly RandomNumberGenerator rndProv;
public CryptoApiRandomGenerator()
{
rndProv = RandomNumberGenerator.Create();
}
public void AddSeedMaterial(byte[] seed) { }
public void AddSeedMaterial(long seed) { }
public void NextBytes(byte[] bytes)
{
rndProv.GetBytes(bytes);
}
public void NextBytes(byte[] bytes, int start, int len)
{
if (start < 0)
throw new ArgumentException("Start offset cannot be negative", "start");
if (bytes.Length < (start + len))
throw new ArgumentException("Byte array too small for requested offset and length");
if (bytes.Length == len && start == 0)
{
NextBytes(bytes);
}
else
{
byte[] tmpBuf = new byte[len];
NextBytes(tmpBuf);
Array.Copy(tmpBuf, 0, bytes, start, len);
}
}
}
}
}
#endif
\ No newline at end of file
using System; #if NET45
using System;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1;
...@@ -81,14 +82,10 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -81,14 +82,10 @@ namespace Titanium.Web.Proxy.Network.Certificate
if (hostName != null) if (hostName != null)
{ {
//add subject alternative names //add subject alternative names
var subjectAlternativeNames = new Asn1Encodable[] var subjectAlternativeNames = new Asn1Encodable[] { new GeneralName(GeneralName.DnsName, hostName), };
{
new GeneralName(GeneralName.DnsName, hostName),
};
var subjectAlternativeNamesExtension = new DerSequence(subjectAlternativeNames); var subjectAlternativeNamesExtension = new DerSequence(subjectAlternativeNames);
certificateGenerator.AddExtension( certificateGenerator.AddExtension(X509Extensions.SubjectAlternativeName.Id, false, subjectAlternativeNamesExtension);
X509Extensions.SubjectAlternativeName.Id, false, subjectAlternativeNamesExtension);
} }
// Subject Public Key // Subject Public Key
var keyGenerationParameters = new KeyGenerationParameters(secureRandom, keyStrength); var keyGenerationParameters = new KeyGenerationParameters(secureRandom, keyStrength);
...@@ -118,7 +115,8 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -118,7 +115,8 @@ namespace Titanium.Web.Proxy.Network.Certificate
} }
var rsa = RsaPrivateKeyStructure.GetInstance(seq); var rsa = RsaPrivateKeyStructure.GetInstance(seq);
var rsaparams = new RsaPrivateCrtKeyParameters(rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent, rsa.Prime1, rsa.Prime2, rsa.Exponent1, rsa.Exponent2, rsa.Coefficient); var rsaparams = new RsaPrivateCrtKeyParameters(rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent, rsa.Prime1, rsa.Prime2, rsa.Exponent1,
rsa.Exponent2, rsa.Coefficient);
// Set private key onto certificate instance // Set private key onto certificate instance
x509Certificate.PrivateKey = DotNetUtilities.ToRSA(rsaparams); x509Certificate.PrivateKey = DotNetUtilities.ToRSA(rsaparams);
...@@ -138,9 +136,8 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -138,9 +136,8 @@ namespace Titanium.Web.Proxy.Network.Certificate
/// <param name="signingCertificate">The signing certificate.</param> /// <param name="signingCertificate">The signing certificate.</param>
/// <returns>X509Certificate2 instance.</returns> /// <returns>X509Certificate2 instance.</returns>
/// <exception cref="System.ArgumentException">You must specify a Signing Certificate if and only if you are not creating a root.</exception> /// <exception cref="System.ArgumentException">You must specify a Signing Certificate if and only if you are not creating a root.</exception>
private X509Certificate2 MakeCertificateInternal(bool isRoot, private X509Certificate2 MakeCertificateInternal(bool isRoot, string hostName, string subjectName, DateTime validFrom, DateTime validTo,
string hostName, string subjectName, X509Certificate2 signingCertificate)
DateTime validFrom, DateTime validTo, X509Certificate2 signingCertificate)
{ {
if (isRoot != (null == signingCertificate)) if (isRoot != (null == signingCertificate))
{ {
...@@ -149,7 +146,8 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -149,7 +146,8 @@ namespace Titanium.Web.Proxy.Network.Certificate
return isRoot return isRoot
? GenerateCertificate(null, subjectName, subjectName, validFrom, validTo) ? GenerateCertificate(null, subjectName, subjectName, validFrom, validTo)
: GenerateCertificate(hostName, subjectName, signingCertificate.Subject, validFrom, validTo, issuerPrivateKey: DotNetUtilities.GetKeyPair(signingCertificate.PrivateKey).Private); : GenerateCertificate(hostName, subjectName, signingCertificate.Subject, validFrom, validTo,
issuerPrivateKey: DotNetUtilities.GetKeyPair(signingCertificate.PrivateKey).Private);
} }
/// <summary> /// <summary>
...@@ -187,7 +185,9 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -187,7 +185,9 @@ namespace Titanium.Web.Proxy.Network.Certificate
return certificate; return certificate;
} }
return MakeCertificateInternal(isRoot, subject, $"CN={subject}", DateTime.UtcNow.AddDays(-certificateGraceDays), DateTime.UtcNow.AddDays(certificateValidDays), isRoot ? null : signingCert); return MakeCertificateInternal(isRoot, subject, $"CN={subject}", DateTime.UtcNow.AddDays(-certificateGraceDays),
DateTime.UtcNow.AddDays(certificateValidDays), isRoot ? null : signingCert);
} }
} }
} }
#endif
\ No newline at end of file
...@@ -239,8 +239,6 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -239,8 +239,6 @@ namespace Titanium.Web.Proxy.Network.Certificate
typeX509Enrollment.InvokeMember("CertificateFriendlyName", BindingFlags.PutDispProperty, null, x509Enrollment, typeValue); typeX509Enrollment.InvokeMember("CertificateFriendlyName", BindingFlags.PutDispProperty, null, x509Enrollment, typeValue);
} }
var members = typeX509Enrollment.GetMembers();
typeValue[0] = 0; typeValue[0] = 0;
var createCertRequest = typeX509Enrollment.InvokeMember("CreateRequest", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue); var createCertRequest = typeX509Enrollment.InvokeMember("CreateRequest", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue);
...@@ -251,7 +249,7 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -251,7 +249,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
try try
{ {
var empty = (string)typeX509Enrollment.InvokeMember("CreatePFX", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue); string empty = (string)typeX509Enrollment.InvokeMember("CreatePFX", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue);
return new X509Certificate2(Convert.FromBase64String(empty), string.Empty, X509KeyStorageFlags.Exportable); return new X509Certificate2(Convert.FromBase64String(empty), string.Empty, X509KeyStorageFlags.Exportable);
} }
catch (Exception) catch (Exception)
...@@ -281,15 +279,15 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -281,15 +279,15 @@ namespace Titanium.Web.Proxy.Network.Certificate
} }
//Subject //Subject
var fullSubject = $"CN={sSubjectCN}"; string fullSubject = $"CN={sSubjectCN}";
//Sig Algo //Sig Algo
var HashAlgo = "SHA256"; string HashAlgo = "SHA256";
//Grace Days //Grace Days
var GraceDays = -366; int GraceDays = -366;
//ValiDays //ValiDays
var ValidDays = 1825; int ValidDays = 1825;
//KeyLength //KeyLength
var keyLength = 2048; int keyLength = 2048;
var graceTime = DateTime.Now.AddDays(GraceDays); var graceTime = DateTime.Now.AddDays(GraceDays);
var now = DateTime.Now; var now = DateTime.Now;
......
...@@ -7,6 +7,7 @@ using System.Linq; ...@@ -7,6 +7,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Network.Certificate; using Titanium.Web.Proxy.Network.Certificate;
...@@ -38,11 +39,13 @@ namespace Titanium.Web.Proxy.Network ...@@ -38,11 +39,13 @@ namespace Titanium.Web.Proxy.Network
get { return engine; } get { return engine; }
set set
{ {
#if NET45
//For Mono only Bouncy Castle is supported //For Mono only Bouncy Castle is supported
if (RunTime.IsRunningOnMono) if (RunTime.IsRunningOnMono)
{ {
value = CertificateEngine.BouncyCastle; value = CertificateEngine.BouncyCastle;
} }
#endif
if (value != engine) if (value != engine)
{ {
...@@ -52,9 +55,11 @@ namespace Titanium.Web.Proxy.Network ...@@ -52,9 +55,11 @@ namespace Titanium.Web.Proxy.Network
if (certEngine == null) if (certEngine == null)
{ {
certEngine = engine == CertificateEngine.BouncyCastle #if NET45
? (ICertificateMaker)new BCCertificateMaker() certEngine = engine == CertificateEngine.BouncyCastle ? (ICertificateMaker)new BCCertificateMaker() : new WinCertificateMaker();
: new WinCertificateMaker(); #else
certEngine = new BCCertificateMaker();
#endif
} }
} }
} }
...@@ -134,7 +139,11 @@ namespace Titanium.Web.Proxy.Network ...@@ -134,7 +139,11 @@ namespace Titanium.Web.Proxy.Network
private string GetRootCertificatePath() private string GetRootCertificatePath()
{ {
var assemblyLocation = Assembly.GetExecutingAssembly().Location; #if NET45
string assemblyLocation = Assembly.GetExecutingAssembly().Location;
#else
string assemblyLocation = string.Empty;
#endif
// dynamically loaded assemblies returns string.Empty location // dynamically loaded assemblies returns string.Empty location
if (assemblyLocation == string.Empty) if (assemblyLocation == string.Empty)
...@@ -142,16 +151,18 @@ namespace Titanium.Web.Proxy.Network ...@@ -142,16 +151,18 @@ namespace Titanium.Web.Proxy.Network
assemblyLocation = Assembly.GetEntryAssembly().Location; assemblyLocation = Assembly.GetEntryAssembly().Location;
} }
var path = Path.GetDirectoryName(assemblyLocation); string path = Path.GetDirectoryName(assemblyLocation);
if (null == path) throw new NullReferenceException(); if (null == path)
var fileName = Path.Combine(path, "rootCert.pfx"); throw new NullReferenceException();
string fileName = Path.Combine(path, "rootCert.pfx");
return fileName; return fileName;
} }
private X509Certificate2 LoadRootCertificate() private X509Certificate2 LoadRootCertificate()
{ {
var fileName = GetRootCertificatePath(); string fileName = GetRootCertificatePath();
if (!File.Exists(fileName)) return null; if (!File.Exists(fileName))
return null;
try try
{ {
return new X509Certificate2(fileName, string.Empty, X509KeyStorageFlags.Exportable); return new X509Certificate2(fileName, string.Empty, X509KeyStorageFlags.Exportable);
...@@ -195,7 +206,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -195,7 +206,7 @@ namespace Titanium.Web.Proxy.Network
{ {
try try
{ {
var fileName = GetRootCertificatePath(); string fileName = GetRootCertificatePath();
File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12)); File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12));
} }
catch (Exception e) catch (Exception e)
...@@ -219,6 +230,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -219,6 +230,7 @@ namespace Titanium.Web.Proxy.Network
TrustRootCertificate(StoreLocation.LocalMachine); TrustRootCertificate(StoreLocation.LocalMachine);
} }
#if NET45
/// <summary> /// <summary>
/// Puts the certificate to the local machine's certificate store. /// Puts the certificate to the local machine's certificate store.
/// Needs elevated permission. Works only on Windows. /// Needs elevated permission. Works only on Windows.
...@@ -231,7 +243,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -231,7 +243,7 @@ namespace Titanium.Web.Proxy.Network
return false; return false;
} }
var fileName = Path.GetTempFileName(); string fileName = Path.GetTempFileName();
File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12)); File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12));
var info = new ProcessStartInfo var info = new ProcessStartInfo
...@@ -263,6 +275,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -263,6 +275,7 @@ namespace Titanium.Web.Proxy.Network
return true; return true;
} }
#endif
/// <summary> /// <summary>
/// Removes the trusted certificates. /// Removes the trusted certificates.
...@@ -276,6 +289,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -276,6 +289,7 @@ namespace Titanium.Web.Proxy.Network
RemoveTrustedRootCertificates(StoreLocation.LocalMachine); RemoveTrustedRootCertificates(StoreLocation.LocalMachine);
} }
#if NET45
/// <summary> /// <summary>
/// Removes the trusted certificates from the local machine's certificate store. /// Removes the trusted certificates from the local machine's certificate store.
/// Needs elevated permission. Works only on Windows. /// Needs elevated permission. Works only on Windows.
...@@ -315,6 +329,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -315,6 +329,7 @@ namespace Titanium.Web.Proxy.Network
return true; return true;
} }
#endif
/// <summary> /// <summary>
/// Determines whether the root certificate is trusted. /// Determines whether the root certificate is trusted.
...@@ -340,7 +355,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -340,7 +355,7 @@ namespace Titanium.Web.Proxy.Network
private X509Certificate2Collection FindCertificates(StoreName storeName, StoreLocation storeLocation, string findValue) private X509Certificate2Collection FindCertificates(StoreName storeName, StoreLocation storeLocation, string findValue)
{ {
X509Store x509Store = new X509Store(storeName, storeLocation); var x509Store = new X509Store(storeName, storeLocation);
try try
{ {
x509Store.Open(OpenFlags.OpenExistingOnly); x509Store.Open(OpenFlags.OpenExistingOnly);
...@@ -348,7 +363,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -348,7 +363,7 @@ namespace Titanium.Web.Proxy.Network
} }
finally finally
{ {
x509Store.Close(); x509Store.Dispose();
} }
} }
...@@ -368,8 +383,11 @@ namespace Titanium.Web.Proxy.Network ...@@ -368,8 +383,11 @@ namespace Titanium.Web.Proxy.Network
} }
X509Certificate2 certificate = null; X509Certificate2 certificate = null;
// todo: lock in netstandard, too
#if NET45
lock (string.Intern(certificateName)) lock (string.Intern(certificateName))
{ {
#endif
if (certificateCache.ContainsKey(certificateName) == false) if (certificateCache.ContainsKey(certificateName) == false)
{ {
try try
...@@ -387,7 +405,10 @@ namespace Titanium.Web.Proxy.Network ...@@ -387,7 +405,10 @@ namespace Titanium.Web.Proxy.Network
} }
if (certificate != null && !certificateCache.ContainsKey(certificateName)) if (certificate != null && !certificateCache.ContainsKey(certificateName))
{ {
certificateCache.Add(certificateName, new CachedCertificate { Certificate = certificate }); certificateCache.Add(certificateName, new CachedCertificate
{
Certificate = certificate
});
} }
} }
else else
...@@ -399,7 +420,9 @@ namespace Titanium.Web.Proxy.Network ...@@ -399,7 +420,9 @@ namespace Titanium.Web.Proxy.Network
return cached.Certificate; return cached.Certificate;
} }
} }
#if NET45
} }
#endif
return certificate; return certificate;
} }
...@@ -422,9 +445,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -422,9 +445,7 @@ namespace Titanium.Web.Proxy.Network
{ {
var cutOff = DateTime.Now.AddMinutes(-1 * certificateCacheTimeOutMinutes); var cutOff = DateTime.Now.AddMinutes(-1 * certificateCacheTimeOutMinutes);
var outdated = certificateCache var outdated = certificateCache.Where(x => x.Value.LastAccess < cutOff).ToList();
.Where(x => x.Value.LastAccess < cutOff)
.ToList();
foreach (var cache in outdated) foreach (var cache in outdated)
certificateCache.Remove(cache.Key); certificateCache.Remove(cache.Key);
...@@ -469,8 +490,8 @@ namespace Titanium.Web.Proxy.Network ...@@ -469,8 +490,8 @@ namespace Titanium.Web.Proxy.Network
} }
finally finally
{ {
x509RootStore.Close(); x509RootStore.Dispose();
x509PersonalStore.Close(); x509PersonalStore.Dispose();
} }
} }
...@@ -509,8 +530,8 @@ namespace Titanium.Web.Proxy.Network ...@@ -509,8 +530,8 @@ namespace Titanium.Web.Proxy.Network
} }
finally finally
{ {
x509RootStore.Close(); x509RootStore.Dispose();
x509PersonalStore.Close(); x509PersonalStore.Dispose();
} }
} }
......
using System; using System;
using System.IO; using System.IO;
using System.Net.Sockets; using System.Net.Sockets;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
...@@ -15,12 +16,16 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -15,12 +16,16 @@ namespace Titanium.Web.Proxy.Network.Tcp
internal ExternalProxy UpStreamHttpsProxy { get; set; } internal ExternalProxy UpStreamHttpsProxy { get; set; }
internal ExternalProxy UpStreamProxy => UseUpstreamProxy ? IsHttps ? UpStreamHttpsProxy : UpStreamHttpProxy : null;
internal string HostName { get; set; } internal string HostName { get; set; }
internal int Port { get; set; } internal int Port { get; set; }
internal bool IsHttps { get; set; } internal bool IsHttps { get; set; }
internal bool UseUpstreamProxy { get; set; }
/// <summary> /// <summary>
/// Http version /// Http version
/// </summary> /// </summary>
...@@ -53,7 +58,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -53,7 +58,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
Stream?.Close(); Stream?.Dispose();
StreamReader?.Dispose(); StreamReader?.Dispose();
...@@ -66,7 +71,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -66,7 +71,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
//It helps to avoid eventual deterioration of performance due to TCP port exhaustion //It helps to avoid eventual deterioration of performance due to TCP port exhaustion
//due to default TCP CLOSE_WAIT timeout for 4 minutes //due to default TCP CLOSE_WAIT timeout for 4 minutes
TcpClient.LingerState = new LingerOption(true, 0); TcpClient.LingerState = new LingerOption(true, 0);
TcpClient.Close(); TcpClient.Dispose();
} }
} }
catch catch
......
...@@ -29,40 +29,21 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -29,40 +29,21 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <param name="externalHttpProxy"></param> /// <param name="externalHttpProxy"></param>
/// <param name="externalHttpsProxy"></param> /// <param name="externalHttpsProxy"></param>
/// <returns></returns> /// <returns></returns>
internal async Task<TcpConnection> CreateClient(ProxyServer server, internal async Task<TcpConnection> CreateClient(ProxyServer server, string remoteHostName, int remotePort, Version httpVersion, bool isHttps,
string remoteHostName, int remotePort, Version httpVersion,
bool isHttps,
ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy) ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy)
{ {
bool useHttpProxy = false; bool useUpstreamProxy = false;
//check if external proxy is set for HTTP var externalProxy = isHttps ? externalHttpsProxy : externalHttpProxy;
if (!isHttps && externalHttpProxy != null
&& !(externalHttpProxy.HostName == remoteHostName
&& externalHttpProxy.Port == remotePort))
{
useHttpProxy = true;
//check if we need to ByPass //check if external proxy is set for HTTP/HTTPS
if (externalHttpProxy.BypassLocalhost if (externalProxy != null && !(externalProxy.HostName == remoteHostName && externalProxy.Port == remotePort))
&& NetworkHelper.IsLocalIpAddress(remoteHostName))
{ {
useHttpProxy = false; useUpstreamProxy = true;
}
}
bool useHttpsProxy = false;
//check if external proxy is set for HTTPS
if (isHttps && externalHttpsProxy != null
&& !(externalHttpsProxy.HostName == remoteHostName
&& externalHttpsProxy.Port == remotePort))
{
useHttpsProxy = true;
//check if we need to ByPass //check if we need to ByPass
if (externalHttpsProxy.BypassLocalhost if (externalProxy.BypassLocalhost && NetworkHelper.IsLocalIpAddress(remoteHostName))
&& NetworkHelper.IsLocalIpAddress(remoteHostName))
{ {
useHttpsProxy = false; useUpstreamProxy = false;
} }
} }
...@@ -71,34 +52,42 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -71,34 +52,42 @@ namespace Titanium.Web.Proxy.Network.Tcp
try try
{ {
if (isHttps) //If this proxy uses another external proxy then create a tunnel request for HTTP/HTTPS connections
{ if (useUpstreamProxy)
//If this proxy uses another external proxy then create a tunnel request for HTTPS connections
if (useHttpsProxy)
{ {
#if NET45
client = new TcpClient(server.UpStreamEndPoint); client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port); #else
client = new TcpClient(server.UpStreamEndPoint.AddressFamily);
#endif
await client.ConnectAsync(externalProxy.HostName, externalProxy.Port);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize); stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
using (var writer = new StreamWriter(stream, Encoding.ASCII, server.BufferSize, true) { NewLine = ProxyConstants.NewLine }) if (isHttps)
{
using (var writer = new StreamWriter(stream, Encoding.ASCII, server.BufferSize, true)
{
NewLine = ProxyConstants.NewLine
})
{ {
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}"); await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}"); await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
await writer.WriteLineAsync("Connection: Keep-Alive"); await writer.WriteLineAsync("Connection: Keep-Alive");
if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null) if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
{ {
await writer.WriteLineAsync("Proxy-Connection: keep-alive"); await writer.WriteLineAsync("Proxy-Connection: keep-alive");
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password))); await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " +
Convert.ToBase64String(Encoding.UTF8.GetBytes(
externalProxy.UserName + ":" + externalProxy.Password)));
} }
await writer.WriteLineAsync(); await writer.WriteLineAsync();
await writer.FlushAsync(); await writer.FlushAsync();
writer.Close();
} }
using (var reader = new CustomBinaryReader(stream, server.BufferSize)) using (var reader = new CustomBinaryReader(stream, server.BufferSize))
{ {
var result = await reader.ReadLineAsync(); string result = await reader.ReadLineAsync();
if (!new[] { "200 OK", "connection established" }.Any(s => result.ContainsIgnoreCase(s))) if (!new[] { "200 OK", "connection established" }.Any(s => result.ContainsIgnoreCase(s)))
{ {
...@@ -108,33 +97,25 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -108,33 +97,25 @@ namespace Titanium.Web.Proxy.Network.Tcp
await reader.ReadAndIgnoreAllLinesAsync(); await reader.ReadAndIgnoreAllLinesAsync();
} }
} }
}
else else
{ {
#if NET45
client = new TcpClient(server.UpStreamEndPoint); client = new TcpClient(server.UpStreamEndPoint);
#else
client = new TcpClient(server.UpStreamEndPoint.AddressFamily);
#endif
await client.ConnectAsync(remoteHostName, remotePort); await client.ConnectAsync(remoteHostName, remotePort);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize); stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
} }
if (isHttps)
{
var sslStream = new SslStream(stream, false, server.ValidateServerCertificate, server.SelectClientCertificate); var sslStream = new SslStream(stream, false, server.ValidateServerCertificate, server.SelectClientCertificate);
stream = new CustomBufferedStream(sslStream, server.BufferSize); stream = new CustomBufferedStream(sslStream, server.BufferSize);
await sslStream.AuthenticateAsClientAsync(remoteHostName, null, server.SupportedSslProtocols, server.CheckCertificateRevocation); await sslStream.AuthenticateAsClientAsync(remoteHostName, null, server.SupportedSslProtocols, server.CheckCertificateRevocation);
} }
else
{
if (useHttpProxy)
{
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(externalHttpProxy.HostName, externalHttpProxy.Port);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
else
{
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(remoteHostName, remotePort);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
}
client.ReceiveTimeout = server.ConnectionTimeOutSeconds * 1000; client.ReceiveTimeout = server.ConnectionTimeOutSeconds * 1000;
client.SendTimeout = server.ConnectionTimeOutSeconds * 1000; client.SendTimeout = server.ConnectionTimeOutSeconds * 1000;
...@@ -142,11 +123,11 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -142,11 +123,11 @@ namespace Titanium.Web.Proxy.Network.Tcp
catch (Exception) catch (Exception)
{ {
stream?.Dispose(); stream?.Dispose();
client?.Close(); client?.Dispose();
throw; throw;
} }
Interlocked.Increment(ref server.serverConnectionCount); server.UpdateServerConnectionCount(true);
return new TcpConnection return new TcpConnection
{ {
...@@ -155,6 +136,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -155,6 +136,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
HostName = remoteHostName, HostName = remoteHostName,
Port = remotePort, Port = remotePort,
IsHttps = isHttps, IsHttps = isHttps,
UseUpstreamProxy = useUpstreamProxy,
TcpClient = client, TcpClient = client,
StreamReader = new CustomBinaryReader(stream, server.BufferSize), StreamReader = new CustomBinaryReader(stream, server.BufferSize),
Stream = stream, Stream = stream,
......
...@@ -11,21 +11,19 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -11,21 +11,19 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </seealso> /// </seealso>
internal class TcpTable : IEnumerable<TcpRow> internal class TcpTable : IEnumerable<TcpRow>
{ {
private readonly IEnumerable<TcpRow> tcpRows;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TcpTable"/> class. /// Initializes a new instance of the <see cref="TcpTable"/> class.
/// </summary> /// </summary>
/// <param name="tcpRows">TcpRow collection to initialize with.</param> /// <param name="tcpRows">TcpRow collection to initialize with.</param>
internal TcpTable(IEnumerable<TcpRow> tcpRows) internal TcpTable(IEnumerable<TcpRow> tcpRows)
{ {
this.tcpRows = tcpRows; this.TcpRows = tcpRows;
} }
/// <summary> /// <summary>
/// Gets the TCP rows. /// Gets the TCP rows.
/// </summary> /// </summary>
internal IEnumerable<TcpRow> TcpRows => tcpRows; internal IEnumerable<TcpRow> TcpRows { get; }
/// <summary> /// <summary>
/// Returns an enumerator that iterates through the collection. /// Returns an enumerator that iterates through the collection.
...@@ -33,7 +31,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -33,7 +31,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <returns>An enumerator that can be used to iterate through the collection.</returns> /// <returns>An enumerator that can be used to iterate through the collection.</returns>
public IEnumerator<TcpRow> GetEnumerator() public IEnumerator<TcpRow> GetEnumerator()
{ {
return tcpRows.GetEnumerator(); return TcpRows.GetEnumerator();
} }
/// <summary> /// <summary>
......
namespace Titanium.Web.Proxy.Network.WinAuth.Security using System;
{ using System.Runtime.InteropServices;
using System;
using System.Runtime.InteropServices;
namespace Titanium.Web.Proxy.Network.WinAuth.Security
{
internal class Common internal class Common
{ {
#region Private constants #region Private constants
private const int ISC_REQ_REPLAY_DETECT = 0x00000004; private const int ISC_REQ_REPLAY_DETECT = 0x00000004;
private const int ISC_REQ_SEQUENCE_DETECT = 0x00000008; private const int ISC_REQ_SEQUENCE_DETECT = 0x00000008;
private const int ISC_REQ_CONFIDENTIALITY = 0x00000010; private const int ISC_REQ_CONFIDENTIALITY = 0x00000010;
private const int ISC_REQ_CONNECTION = 0x00000800; private const int ISC_REQ_CONNECTION = 0x00000800;
#endregion #endregion
internal static uint NewContextAttributes = 0; internal static uint NewContextAttributes = 0;
internal static Common.SecurityInteger NewLifeTime = new SecurityInteger(0); internal static SecurityInteger NewLifeTime = new SecurityInteger(0);
#region internal constants #region internal constants
internal const int StandardContextAttributes = ISC_REQ_CONFIDENTIALITY | ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT | ISC_REQ_CONNECTION; internal const int StandardContextAttributes = ISC_REQ_CONFIDENTIALITY | ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT | ISC_REQ_CONNECTION;
internal const int SecurityNativeDataRepresentation = 0x10; internal const int SecurityNativeDataRepresentation = 0x10;
internal const int MaximumTokenSize = 12288; internal const int MaximumTokenSize = 12288;
internal const int SecurityCredentialsOutbound = 2; internal const int SecurityCredentialsOutbound = 2;
internal const int SuccessfulResult = 0; internal const int SuccessfulResult = 0;
internal const int IntermediateResult = 0x90312; internal const int IntermediateResult = 0x90312;
#endregion #endregion
#region internal enumerations #region internal enumerations
internal enum SecurityBufferType internal enum SecurityBufferType
{ {
SECBUFFER_VERSION = 0, SECBUFFER_VERSION = 0,
...@@ -34,26 +39,35 @@ ...@@ -34,26 +39,35 @@
} }
[Flags] [Flags]
internal enum NtlmFlags : int internal enum NtlmFlags
{ {
// The client sets this flag to indicate that it supports Unicode strings. // The client sets this flag to indicate that it supports Unicode strings.
NegotiateUnicode = 0x00000001, NegotiateUnicode = 0x00000001,
// This is set to indicate that the client supports OEM strings. // This is set to indicate that the client supports OEM strings.
NegotiateOem = 0x00000002, NegotiateOem = 0x00000002,
// This requests that the server send the authentication target with the Type 2 reply. // This requests that the server send the authentication target with the Type 2 reply.
RequestTarget = 0x00000004, RequestTarget = 0x00000004,
// Indicates that NTLM authentication is supported. // Indicates that NTLM authentication is supported.
NegotiateNtlm = 0x00000200, NegotiateNtlm = 0x00000200,
// When set, the client will send with the message the name of the domain in which the workstation has membership. // When set, the client will send with the message the name of the domain in which the workstation has membership.
NegotiateDomainSupplied = 0x00001000, NegotiateDomainSupplied = 0x00001000,
// Indicates that the client is sending its workstation name with the message. // Indicates that the client is sending its workstation name with the message.
NegotiateWorkstationSupplied = 0x00002000, NegotiateWorkstationSupplied = 0x00002000,
// Indicates that communication between the client and server after authentication should carry a "dummy" signature. // Indicates that communication between the client and server after authentication should carry a "dummy" signature.
NegotiateAlwaysSign = 0x00008000, NegotiateAlwaysSign = 0x00008000,
// Indicates that this client supports the NTLM2 signing and sealing scheme; if negotiated, this can also affect the response calculations. // Indicates that this client supports the NTLM2 signing and sealing scheme; if negotiated, this can also affect the response calculations.
NegotiateNtlm2Key = 0x00080000, NegotiateNtlm2Key = 0x00080000,
// Indicates that this client supports strong (128-bit) encryption. // Indicates that this client supports strong (128-bit) encryption.
Negotiate128 = 0x20000000, Negotiate128 = 0x20000000,
// Indicates that this client supports medium (56-bit) encryption. // Indicates that this client supports medium (56-bit) encryption.
Negotiate56 = (unchecked((int)0x80000000)) Negotiate56 = (unchecked((int)0x80000000))
} }
...@@ -74,9 +88,11 @@ ...@@ -74,9 +88,11 @@
/* Use NTLMv2 only. */ /* Use NTLMv2 only. */
NTLMv2_only, NTLMv2_only,
} }
#endregion #endregion
#region internal structures #region internal structures
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal struct SecurityHandle internal struct SecurityHandle
{ {
...@@ -102,6 +118,7 @@ ...@@ -102,6 +118,7 @@
{ {
internal uint LowPart; internal uint LowPart;
internal int HighPart; internal int HighPart;
internal SecurityInteger(int dummy) internal SecurityInteger(int dummy)
{ {
LowPart = 0; LowPart = 0;
...@@ -152,7 +169,6 @@ ...@@ -152,7 +169,6 @@
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal struct SecurityBufferDesciption : IDisposable internal struct SecurityBufferDesciption : IDisposable
{ {
internal int ulVersion; internal int ulVersion;
internal int cBuffers; internal int cBuffers;
internal IntPtr pBuffers; //Point to SecBuffer internal IntPtr pBuffers; //Point to SecBuffer
...@@ -161,18 +177,18 @@ ...@@ -161,18 +177,18 @@
{ {
ulVersion = (int)SecurityBufferType.SECBUFFER_VERSION; ulVersion = (int)SecurityBufferType.SECBUFFER_VERSION;
cBuffers = 1; cBuffers = 1;
Common.SecurityBuffer ThisSecBuffer = new Common.SecurityBuffer(bufferSize); var thisSecBuffer = new SecurityBuffer(bufferSize);
pBuffers = Marshal.AllocHGlobal(Marshal.SizeOf(ThisSecBuffer)); pBuffers = Marshal.AllocHGlobal(Marshal.SizeOf(thisSecBuffer));
Marshal.StructureToPtr(ThisSecBuffer, pBuffers, false); Marshal.StructureToPtr(thisSecBuffer, pBuffers, false);
} }
internal SecurityBufferDesciption(byte[] secBufferBytes) internal SecurityBufferDesciption(byte[] secBufferBytes)
{ {
ulVersion = (int)SecurityBufferType.SECBUFFER_VERSION; ulVersion = (int)SecurityBufferType.SECBUFFER_VERSION;
cBuffers = 1; cBuffers = 1;
Common.SecurityBuffer ThisSecBuffer = new Common.SecurityBuffer(secBufferBytes); var thisSecBuffer = new SecurityBuffer(secBufferBytes);
pBuffers = Marshal.AllocHGlobal(Marshal.SizeOf(ThisSecBuffer)); pBuffers = Marshal.AllocHGlobal(Marshal.SizeOf(thisSecBuffer));
Marshal.StructureToPtr(ThisSecBuffer, pBuffers, false); Marshal.StructureToPtr(thisSecBuffer, pBuffers, false);
} }
public void Dispose() public void Dispose()
...@@ -181,12 +197,12 @@ ...@@ -181,12 +197,12 @@
{ {
if (cBuffers == 1) if (cBuffers == 1)
{ {
Common.SecurityBuffer ThisSecBuffer = (Common.SecurityBuffer)Marshal.PtrToStructure(pBuffers, typeof(Common.SecurityBuffer)); var thisSecBuffer = (SecurityBuffer)Marshal.PtrToStructure(pBuffers, typeof(SecurityBuffer));
ThisSecBuffer.Dispose(); thisSecBuffer.Dispose();
} }
else else
{ {
for (int Index = 0; Index < cBuffers; Index++) for (int index = 0; index < cBuffers; index++)
{ {
//The bits were written out the following order: //The bits were written out the following order:
//int cbBuffer; //int cbBuffer;
...@@ -194,9 +210,9 @@ ...@@ -194,9 +210,9 @@
//pvBuffer; //pvBuffer;
//What we need to do here is to grab a hold of the pvBuffer allocate by the individual //What we need to do here is to grab a hold of the pvBuffer allocate by the individual
//SecBuffer and release it... //SecBuffer and release it...
int CurrentOffset = Index * Marshal.SizeOf(typeof(Buffer)); int currentOffset = index * Marshal.SizeOf(typeof(Buffer));
IntPtr SecBufferpvBuffer = Marshal.ReadIntPtr(pBuffers, CurrentOffset + Marshal.SizeOf(typeof(int)) + Marshal.SizeOf(typeof(int))); var secBufferpvBuffer = Marshal.ReadIntPtr(pBuffers, currentOffset + Marshal.SizeOf(typeof(int)) + Marshal.SizeOf(typeof(int)));
Marshal.FreeHGlobal(SecBufferpvBuffer); Marshal.FreeHGlobal(secBufferpvBuffer);
} }
} }
...@@ -207,7 +223,7 @@ ...@@ -207,7 +223,7 @@
internal byte[] GetBytes() internal byte[] GetBytes()
{ {
byte[] Buffer = null; byte[] buffer = null;
if (pBuffers == IntPtr.Zero) if (pBuffers == IntPtr.Zero)
{ {
...@@ -216,32 +232,32 @@ ...@@ -216,32 +232,32 @@
if (cBuffers == 1) if (cBuffers == 1)
{ {
Common.SecurityBuffer ThisSecBuffer = (Common.SecurityBuffer)Marshal.PtrToStructure(pBuffers, typeof(Common.SecurityBuffer)); var thisSecBuffer = (SecurityBuffer)Marshal.PtrToStructure(pBuffers, typeof(SecurityBuffer));
if (ThisSecBuffer.cbBuffer > 0) if (thisSecBuffer.cbBuffer > 0)
{ {
Buffer = new byte[ThisSecBuffer.cbBuffer]; buffer = new byte[thisSecBuffer.cbBuffer];
Marshal.Copy(ThisSecBuffer.pvBuffer, Buffer, 0, ThisSecBuffer.cbBuffer); Marshal.Copy(thisSecBuffer.pvBuffer, buffer, 0, thisSecBuffer.cbBuffer);
} }
} }
else else
{ {
int BytesToAllocate = 0; int bytesToAllocate = 0;
for (int Index = 0; Index < cBuffers; Index++) for (int index = 0; index < cBuffers; index++)
{ {
//The bits were written out the following order: //The bits were written out the following order:
//int cbBuffer; //int cbBuffer;
//int BufferType; //int BufferType;
//pvBuffer; //pvBuffer;
//What we need to do here calculate the total number of bytes we need to copy... //What we need to do here calculate the total number of bytes we need to copy...
int CurrentOffset = Index * Marshal.SizeOf(typeof(Buffer)); int currentOffset = index * Marshal.SizeOf(typeof(Buffer));
BytesToAllocate += Marshal.ReadInt32(pBuffers, CurrentOffset); bytesToAllocate += Marshal.ReadInt32(pBuffers, currentOffset);
} }
Buffer = new byte[BytesToAllocate]; buffer = new byte[bytesToAllocate];
for (int Index = 0, BufferIndex = 0; Index < cBuffers; Index++) for (int index = 0, bufferIndex = 0; index < cBuffers; index++)
{ {
//The bits were written out the following order: //The bits were written out the following order:
//int cbBuffer; //int cbBuffer;
...@@ -249,17 +265,18 @@ ...@@ -249,17 +265,18 @@
//pvBuffer; //pvBuffer;
//Now iterate over the individual buffers and put them together into a //Now iterate over the individual buffers and put them together into a
//byte array... //byte array...
int CurrentOffset = Index * Marshal.SizeOf(typeof(Buffer)); int currentOffset = index * Marshal.SizeOf(typeof(Buffer));
int BytesToCopy = Marshal.ReadInt32(pBuffers, CurrentOffset); int bytesToCopy = Marshal.ReadInt32(pBuffers, currentOffset);
IntPtr SecBufferpvBuffer = Marshal.ReadIntPtr(pBuffers, CurrentOffset + Marshal.SizeOf(typeof(int)) + Marshal.SizeOf(typeof(int))); var secBufferpvBuffer = Marshal.ReadIntPtr(pBuffers, currentOffset + Marshal.SizeOf(typeof(int)) + Marshal.SizeOf(typeof(int)));
Marshal.Copy(SecBufferpvBuffer, Buffer, BufferIndex, BytesToCopy); Marshal.Copy(secBufferpvBuffer, buffer, bufferIndex, bytesToCopy);
BufferIndex += BytesToCopy; bufferIndex += bytesToCopy;
} }
} }
return (Buffer); return (buffer);
} }
} }
#endregion #endregion
} }
} }
...@@ -27,229 +27,224 @@ ...@@ -27,229 +27,224 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// //
using System;
namespace Titanium.Web.Proxy.Network.WinAuth.Security namespace Titanium.Web.Proxy.Network.WinAuth.Security
{ {
using System;
internal sealed class LittleEndian internal sealed class LittleEndian
{ {
private LittleEndian () private LittleEndian()
{ {
} }
unsafe private static byte[] GetUShortBytes (byte *bytes) private static unsafe byte[] GetUShortBytes(byte*bytes)
{ {
if (BitConverter.IsLittleEndian) if (BitConverter.IsLittleEndian)
{ {
return new byte[] { bytes[0], bytes[1] }; return new[] { bytes[0], bytes[1] };
}
else
{
return new byte[] { bytes[1], bytes[0] };
} }
return new[] { bytes[1], bytes[0] };
} }
unsafe private static byte[] GetUIntBytes (byte *bytes) private static unsafe byte[] GetUIntBytes(byte*bytes)
{ {
if (BitConverter.IsLittleEndian) if (BitConverter.IsLittleEndian)
{ {
return new byte[] { bytes[0], bytes[1], bytes[2], bytes[3] }; return new[] { bytes[0], bytes[1], bytes[2], bytes[3] };
}
else
{
return new byte[] { bytes[3], bytes[2], bytes[1], bytes[0] };
} }
return new[] { bytes[3], bytes[2], bytes[1], bytes[0] };
} }
unsafe private static byte[] GetULongBytes (byte *bytes) private static unsafe byte[] GetULongBytes(byte*bytes)
{ {
if (BitConverter.IsLittleEndian) if (BitConverter.IsLittleEndian)
{ {
return new byte[] { bytes [0], bytes [1], bytes [2], bytes [3], return new[] { bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7] };
bytes [4], bytes [5], bytes [6], bytes [7] };
}
else
{
return new byte[] { bytes [7], bytes [6], bytes [5], bytes [4],
bytes [3], bytes [2], bytes [1], bytes [0] };
} }
return new[] { bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0] };
} }
unsafe internal static byte[] GetBytes (bool value) internal static byte[] GetBytes(bool value)
{ {
return new byte [] { value ? (byte)1 : (byte)0 }; return new[] { value ? (byte)1 : (byte)0 };
} }
unsafe internal static byte[] GetBytes (char value) internal static unsafe byte[] GetBytes(char value)
{ {
return GetUShortBytes ((byte *) &value); return GetUShortBytes((byte*)&value);
} }
unsafe internal static byte[] GetBytes (short value) internal static unsafe byte[] GetBytes(short value)
{ {
return GetUShortBytes ((byte *) &value); return GetUShortBytes((byte*)&value);
} }
unsafe internal static byte[] GetBytes (int value) internal static unsafe byte[] GetBytes(int value)
{ {
return GetUIntBytes ((byte *) &value); return GetUIntBytes((byte*)&value);
} }
unsafe internal static byte[] GetBytes (long value) internal static unsafe byte[] GetBytes(long value)
{ {
return GetULongBytes ((byte *) &value); return GetULongBytes((byte*)&value);
} }
unsafe internal static byte[] GetBytes (ushort value) internal static unsafe byte[] GetBytes(ushort value)
{ {
return GetUShortBytes ((byte *) &value); return GetUShortBytes((byte*)&value);
} }
unsafe internal static byte[] GetBytes (uint value) internal static unsafe byte[] GetBytes(uint value)
{ {
return GetUIntBytes ((byte *) &value); return GetUIntBytes((byte*)&value);
} }
unsafe internal static byte[] GetBytes (ulong value) internal static unsafe byte[] GetBytes(ulong value)
{ {
return GetULongBytes ((byte *) &value); return GetULongBytes((byte*)&value);
} }
unsafe internal static byte[] GetBytes (float value) internal static unsafe byte[] GetBytes(float value)
{ {
return GetUIntBytes ((byte *) &value); return GetUIntBytes((byte*)&value);
} }
unsafe internal static byte[] GetBytes (double value) internal static unsafe byte[] GetBytes(double value)
{ {
return GetULongBytes ((byte *) &value); return GetULongBytes((byte*)&value);
} }
unsafe private static void UShortFromBytes (byte *dst, byte[] src, int startIndex) private static unsafe void UShortFromBytes(byte*dst, byte[] src, int startIndex)
{ {
if (BitConverter.IsLittleEndian) if (BitConverter.IsLittleEndian)
{ {
dst [0] = src [startIndex]; dst[0] = src[startIndex];
dst [1] = src [startIndex + 1]; dst[1] = src[startIndex + 1];
} }
else else
{ {
dst [0] = src [startIndex + 1]; dst[0] = src[startIndex + 1];
dst [1] = src [startIndex]; dst[1] = src[startIndex];
} }
} }
unsafe private static void UIntFromBytes (byte *dst, byte[] src, int startIndex) private static unsafe void UIntFromBytes(byte*dst, byte[] src, int startIndex)
{ {
if (BitConverter.IsLittleEndian) if (BitConverter.IsLittleEndian)
{ {
dst [0] = src [startIndex]; dst[0] = src[startIndex];
dst [1] = src [startIndex + 1]; dst[1] = src[startIndex + 1];
dst [2] = src [startIndex + 2]; dst[2] = src[startIndex + 2];
dst [3] = src [startIndex + 3]; dst[3] = src[startIndex + 3];
} }
else else
{ {
dst [0] = src [startIndex + 3]; dst[0] = src[startIndex + 3];
dst [1] = src [startIndex + 2]; dst[1] = src[startIndex + 2];
dst [2] = src [startIndex + 1]; dst[2] = src[startIndex + 1];
dst [3] = src [startIndex]; dst[3] = src[startIndex];
} }
} }
unsafe private static void ULongFromBytes (byte *dst, byte[] src, int startIndex) private static unsafe void ULongFromBytes(byte*dst, byte[] src, int startIndex)
{
if (BitConverter.IsLittleEndian)
{ {
if (BitConverter.IsLittleEndian) {
for (int i = 0; i < 8; ++i) for (int i = 0; i < 8; ++i)
dst [i] = src [startIndex + i]; dst[i] = src[startIndex + i];
} else { }
else
{
for (int i = 0; i < 8; ++i) for (int i = 0; i < 8; ++i)
dst [i] = src [startIndex + (7 - i)]; dst[i] = src[startIndex + (7 - i)];
} }
} }
unsafe internal static bool ToBoolean (byte[] value, int startIndex) internal static bool ToBoolean(byte[] value, int startIndex)
{ {
return value [startIndex] != 0; return value[startIndex] != 0;
} }
unsafe internal static char ToChar (byte[] value, int startIndex) internal static unsafe char ToChar(byte[] value, int startIndex)
{ {
char ret; char ret;
UShortFromBytes ((byte *) &ret, value, startIndex); UShortFromBytes((byte*)&ret, value, startIndex);
return ret; return ret;
} }
unsafe internal static short ToInt16 (byte[] value, int startIndex) internal static unsafe short ToInt16(byte[] value, int startIndex)
{ {
short ret; short ret;
UShortFromBytes ((byte *) &ret, value, startIndex); UShortFromBytes((byte*)&ret, value, startIndex);
return ret; return ret;
} }
unsafe internal static int ToInt32 (byte[] value, int startIndex) internal static unsafe int ToInt32(byte[] value, int startIndex)
{ {
int ret; int ret;
UIntFromBytes ((byte *) &ret, value, startIndex); UIntFromBytes((byte*)&ret, value, startIndex);
return ret; return ret;
} }
unsafe internal static long ToInt64 (byte[] value, int startIndex) internal static unsafe long ToInt64(byte[] value, int startIndex)
{ {
long ret; long ret;
ULongFromBytes ((byte *) &ret, value, startIndex); ULongFromBytes((byte*)&ret, value, startIndex);
return ret; return ret;
} }
unsafe internal static ushort ToUInt16 (byte[] value, int startIndex) internal static unsafe ushort ToUInt16(byte[] value, int startIndex)
{ {
ushort ret; ushort ret;
UShortFromBytes ((byte *) &ret, value, startIndex); UShortFromBytes((byte*)&ret, value, startIndex);
return ret; return ret;
} }
unsafe internal static uint ToUInt32 (byte[] value, int startIndex) internal static unsafe uint ToUInt32(byte[] value, int startIndex)
{ {
uint ret; uint ret;
UIntFromBytes ((byte *) &ret, value, startIndex); UIntFromBytes((byte*)&ret, value, startIndex);
return ret; return ret;
} }
unsafe internal static ulong ToUInt64 (byte[] value, int startIndex) internal static unsafe ulong ToUInt64(byte[] value, int startIndex)
{ {
ulong ret; ulong ret;
ULongFromBytes ((byte *) &ret, value, startIndex); ULongFromBytes((byte*)&ret, value, startIndex);
return ret; return ret;
} }
unsafe internal static float ToSingle (byte[] value, int startIndex) internal static unsafe float ToSingle(byte[] value, int startIndex)
{ {
float ret; float ret;
UIntFromBytes ((byte *) &ret, value, startIndex); UIntFromBytes((byte*)&ret, value, startIndex);
return ret; return ret;
} }
unsafe internal static double ToDouble (byte[] value, int startIndex) internal static unsafe double ToDouble(byte[] value, int startIndex)
{ {
double ret; double ret;
ULongFromBytes ((byte *) &ret, value, startIndex); ULongFromBytes((byte*)&ret, value, startIndex);
return ret; return ret;
} }
......
...@@ -33,50 +33,37 @@ ...@@ -33,50 +33,37 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// //
using System;
using System.Text;
namespace Titanium.Web.Proxy.Network.WinAuth.Security namespace Titanium.Web.Proxy.Network.WinAuth.Security
{ {
using System;
using System.Text;
internal class Message internal class Message
{ {
static private byte[] header = { 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00 }; private static readonly byte[] header = { 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00 };
internal Message (byte[] message) internal Message(byte[] message)
{ {
_type = 3; type = 3;
Decode (message); Decode(message);
} }
/// <summary> /// <summary>
/// Domain name /// Domain name
/// </summary> /// </summary>
internal string Domain internal string Domain { get; private set; }
{
get;
private set;
}
/// <summary> /// <summary>
/// Username /// Username
/// </summary> /// </summary>
internal string Username internal string Username { get; private set; }
{
get;
private set;
}
private int _type; private readonly int type;
private Common.NtlmFlags _flags;
internal Common.NtlmFlags Flags internal Common.NtlmFlags Flags { get; set; }
{
get { return _flags; }
set { _flags = value; }
}
// methods // methods
private void Decode (byte[] message) private void Decode(byte[] message)
{ {
//base.Decode (message); //base.Decode (message);
...@@ -95,10 +82,10 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -95,10 +82,10 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
throw new ArgumentException(msg, "message"); throw new ArgumentException(msg, "message");
} }
if (LittleEndian.ToUInt16 (message, 56) != message.Length) if (LittleEndian.ToUInt16(message, 56) != message.Length)
{ {
string msg = "Invalid Type3 message length."; string msg = "Invalid Type3 message length.";
throw new ArgumentException (msg, "message"); throw new ArgumentException(msg, "message");
} }
if (message.Length >= 64) if (message.Length >= 64)
...@@ -110,28 +97,25 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -110,28 +97,25 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
Flags = (Common.NtlmFlags)0x8201; Flags = (Common.NtlmFlags)0x8201;
} }
int dom_len = LittleEndian.ToUInt16 (message, 28); int domLen = LittleEndian.ToUInt16(message, 28);
int dom_off = LittleEndian.ToUInt16 (message, 32); int domOff = LittleEndian.ToUInt16(message, 32);
this.Domain = DecodeString (message, dom_off, dom_len); Domain = DecodeString(message, domOff, domLen);
int user_len = LittleEndian.ToUInt16 (message, 36); int userLen = LittleEndian.ToUInt16(message, 36);
int user_off = LittleEndian.ToUInt16 (message, 40); int userOff = LittleEndian.ToUInt16(message, 40);
this.Username = DecodeString (message, user_off, user_len); Username = DecodeString(message, userOff, userLen);
} }
string DecodeString (byte[] buffer, int offset, int len) string DecodeString(byte[] buffer, int offset, int len)
{ {
if ((Flags & Common.NtlmFlags.NegotiateUnicode) != 0) if ((Flags & Common.NtlmFlags.NegotiateUnicode) != 0)
{ {
return Encoding.Unicode.GetString(buffer, offset, len); return Encoding.Unicode.GetString(buffer, offset, len);
} }
else
{
return Encoding.ASCII.GetString(buffer, offset, len); return Encoding.ASCII.GetString(buffer, offset, len);
} }
}
protected bool CheckHeader(byte[] message) protected bool CheckHeader(byte[] message)
{ {
...@@ -140,8 +124,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -140,8 +124,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
if (message[i] != header[i]) if (message[i] != header[i])
return false; return false;
} }
return (LittleEndian.ToUInt32(message, 8) == _type); return (LittleEndian.ToUInt32(message, 8) == type);
} }
} }
} }
namespace Titanium.Web.Proxy.Network.WinAuth.Security using System;
{
using System;
namespace Titanium.Web.Proxy.Network.WinAuth.Security
{
/// <summary> /// <summary>
/// Status of authenticated session /// Status of authenticated session
/// </summary> /// </summary>
...@@ -9,10 +9,10 @@ ...@@ -9,10 +9,10 @@
{ {
internal State() internal State()
{ {
this.Credentials = new Common.SecurityHandle(0); Credentials = new Common.SecurityHandle(0);
this.Context = new Common.SecurityHandle(0); Context = new Common.SecurityHandle(0);
this.LastSeen = DateTime.Now; LastSeen = DateTime.Now;
} }
/// <summary> /// <summary>
...@@ -32,13 +32,13 @@ ...@@ -32,13 +32,13 @@
internal void ResetHandles() internal void ResetHandles()
{ {
this.Credentials.Reset(); Credentials.Reset();
this.Context.Reset(); Context.Reset();
} }
internal void UpdatePresence() internal void UpdatePresence()
{ {
this.LastSeen = DateTime.Now; LastSeen = DateTime.Now;
} }
} }
} }
// http://pinvoke.net/default.aspx/secur32/InitializeSecurityContext.html // http://pinvoke.net/default.aspx/secur32/InitializeSecurityContext.html
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Network.WinAuth.Security namespace Titanium.Web.Proxy.Network.WinAuth.Security
{ {
using System;
using System.Linq;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security.Principal;
using static Common; using static Common;
using System.Threading.Tasks;
internal class WinAuthEndPoint internal class WinAuthEndPoint
{ {
/// <summary> /// <summary>
/// Keep track of auth states for reuse in final challenge response /// Keep track of auth states for reuse in final challenge response
/// </summary> /// </summary>
private static IDictionary<Guid, State> authStates private static readonly IDictionary<Guid, State> authStates = new ConcurrentDictionary<Guid, State>();
= new ConcurrentDictionary<Guid, State>();
/// <summary> /// <summary>
...@@ -27,17 +27,14 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -27,17 +27,14 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
/// <param name="authScheme"></param> /// <param name="authScheme"></param>
/// <param name="requestId"></param> /// <param name="requestId"></param>
/// <returns></returns> /// <returns></returns>
internal static byte[] AcquireInitialSecurityToken(string hostname, internal static byte[] AcquireInitialSecurityToken(string hostname, string authScheme, Guid requestId)
string authScheme, Guid requestId)
{ {
byte[] token = null; byte[] token;
//null for initial call //null for initial call
SecurityBufferDesciption serverToken var serverToken = new SecurityBufferDesciption();
= new SecurityBufferDesciption();
SecurityBufferDesciption clientToken var clientToken = new SecurityBufferDesciption(MaximumTokenSize);
= new SecurityBufferDesciption(MaximumTokenSize);
try try
{ {
...@@ -101,17 +98,14 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -101,17 +98,14 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
/// <param name="serverChallenge"></param> /// <param name="serverChallenge"></param>
/// <param name="requestId"></param> /// <param name="requestId"></param>
/// <returns></returns> /// <returns></returns>
internal static byte[] AcquireFinalSecurityToken(string hostname, internal static byte[] AcquireFinalSecurityToken(string hostname, byte[] serverChallenge, Guid requestId)
byte[] serverChallenge, Guid requestId)
{ {
byte[] token = null; byte[] token;
//user server challenge //user server challenge
SecurityBufferDesciption serverToken var serverToken = new SecurityBufferDesciption(serverChallenge);
= new SecurityBufferDesciption(serverChallenge);
SecurityBufferDesciption clientToken var clientToken = new SecurityBufferDesciption(MaximumTokenSize);
= new SecurityBufferDesciption(MaximumTokenSize);
try try
{ {
...@@ -161,9 +155,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -161,9 +155,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
{ {
var cutOff = DateTime.Now.AddMinutes(-1 * stateCacheTimeOutMinutes); var cutOff = DateTime.Now.AddMinutes(-1 * stateCacheTimeOutMinutes);
var outdated = authStates var outdated = authStates.Where(x => x.Value.LastSeen < cutOff).ToList();
.Where(x => x.Value.LastSeen < cutOff)
.ToList();
foreach (var cache in outdated) foreach (var cache in outdated)
{ {
...@@ -177,28 +169,28 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -177,28 +169,28 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
#region Native calls to secur32.dll #region Native calls to secur32.dll
[DllImport("secur32.dll", SetLastError = true)] [DllImport("secur32.dll", SetLastError = true)]
static extern int InitializeSecurityContext(ref SecurityHandle phCredential,//PCredHandle static extern int InitializeSecurityContext(ref SecurityHandle phCredential, //PCredHandle
IntPtr phContext, //PCtxtHandle IntPtr phContext, //PCtxtHandle
string pszTargetName, string pszTargetName,
int fContextReq, int fContextReq,
int Reserved1, int reserved1,
int TargetDataRep, int targetDataRep,
ref SecurityBufferDesciption pInput, //PSecBufferDesc SecBufferDesc ref SecurityBufferDesciption pInput, //PSecBufferDesc SecBufferDesc
int Reserved2, int reserved2,
out SecurityHandle phNewContext, //PCtxtHandle out SecurityHandle phNewContext, //PCtxtHandle
out SecurityBufferDesciption pOutput, //PSecBufferDesc SecBufferDesc out SecurityBufferDesciption pOutput, //PSecBufferDesc SecBufferDesc
out uint pfContextAttr, //managed ulong == 64 bits!!! out uint pfContextAttr, //managed ulong == 64 bits!!!
out SecurityInteger ptsExpiry); //PTimeStamp out SecurityInteger ptsExpiry); //PTimeStamp
[DllImport("secur32", CharSet = CharSet.Auto, SetLastError = true)] [DllImport("secur32", CharSet = CharSet.Auto, SetLastError = true)]
static extern int InitializeSecurityContext(ref SecurityHandle phCredential,//PCredHandle static extern int InitializeSecurityContext(ref SecurityHandle phCredential, //PCredHandle
ref SecurityHandle phContext, //PCtxtHandle ref SecurityHandle phContext, //PCtxtHandle
string pszTargetName, string pszTargetName,
int fContextReq, int fContextReq,
int Reserved1, int reserved1,
int TargetDataRep, int targetDataRep,
ref SecurityBufferDesciption SecBufferDesc, //PSecBufferDesc SecBufferDesc ref SecurityBufferDesciption secBufferDesc, //PSecBufferDesc SecBufferDesc
int Reserved2, int reserved2,
out SecurityHandle phNewContext, //PCtxtHandle out SecurityHandle phNewContext, //PCtxtHandle
out SecurityBufferDesciption pOutput, //PSecBufferDesc SecBufferDesc out SecurityBufferDesciption pOutput, //PSecBufferDesc SecBufferDesc
out uint pfContextAttr, //managed ulong == 64 bits!!! out uint pfContextAttr, //managed ulong == 64 bits!!!
...@@ -209,13 +201,12 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -209,13 +201,12 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
string pszPrincipal, //SEC_CHAR* string pszPrincipal, //SEC_CHAR*
string pszPackage, //SEC_CHAR* //"Kerberos","NTLM","Negotiative" string pszPackage, //SEC_CHAR* //"Kerberos","NTLM","Negotiative"
int fCredentialUse, int fCredentialUse,
IntPtr PAuthenticationID, //_LUID AuthenticationID,//pvLogonID, //PLUID IntPtr pAuthenticationId, //_LUID AuthenticationID,//pvLogonID, //PLUID
IntPtr pAuthData, //PVOID IntPtr pAuthData, //PVOID
int pGetKeyFn, //SEC_GET_KEY_FN int pGetKeyFn, //SEC_GET_KEY_FN
IntPtr pvGetKeyArgument, //PVOID IntPtr pvGetKeyArgument, //PVOID
ref Common.SecurityHandle phCredential, //SecHandle //PCtxtHandle ref ref SecurityHandle phCredential, //SecHandle //PCtxtHandle ref
ref Common.SecurityInteger ptsExpiry); //PTimeStamp //TimeStamp ref ref SecurityInteger ptsExpiry); //PTimeStamp //TimeStamp ref
#endregion #endregion
} }
......
using Titanium.Web.Proxy.Network.WinAuth.Security; using System;
using System; using Titanium.Web.Proxy.Network.WinAuth.Security;
namespace Titanium.Web.Proxy.Network.WinAuth namespace Titanium.Web.Proxy.Network.WinAuth
{ {
...@@ -18,8 +18,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth ...@@ -18,8 +18,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth
/// <param name="authScheme"></param> /// <param name="authScheme"></param>
/// <param name="requestId"></param> /// <param name="requestId"></param>
/// <returns></returns> /// <returns></returns>
public static string GetInitialAuthToken(string serverHostname, public static string GetInitialAuthToken(string serverHostname, string authScheme, Guid requestId)
string authScheme, Guid requestId)
{ {
var tokenBytes = WinAuthEndPoint.AcquireInitialSecurityToken(serverHostname, authScheme, requestId); var tokenBytes = WinAuthEndPoint.AcquireInitialSecurityToken(serverHostname, authScheme, requestId);
return string.Concat(" ", Convert.ToBase64String(tokenBytes)); return string.Concat(" ", Convert.ToBase64String(tokenBytes));
...@@ -33,14 +32,11 @@ namespace Titanium.Web.Proxy.Network.WinAuth ...@@ -33,14 +32,11 @@ namespace Titanium.Web.Proxy.Network.WinAuth
/// <param name="serverToken"></param> /// <param name="serverToken"></param>
/// <param name="requestId"></param> /// <param name="requestId"></param>
/// <returns></returns> /// <returns></returns>
public static string GetFinalAuthToken(string serverHostname, public static string GetFinalAuthToken(string serverHostname, string serverToken, Guid requestId)
string serverToken, Guid requestId)
{ {
var tokenBytes = WinAuthEndPoint.AcquireFinalSecurityToken(serverHostname, var tokenBytes = WinAuthEndPoint.AcquireFinalSecurityToken(serverHostname, Convert.FromBase64String(serverToken), requestId);
Convert.FromBase64String(serverToken), requestId);
return string.Concat(" ", Convert.ToBase64String(tokenBytes)); return string.Concat(" ", Convert.ToBase64String(tokenBytes));
} }
} }
} }
...@@ -11,7 +11,7 @@ using System.Runtime.InteropServices; ...@@ -11,7 +11,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")] [assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Titanium.Web.Proxy.Properties")] [assembly: AssemblyProduct("Titanium.Web.Proxy.Properties")]
[assembly: AssemblyCopyright("Copyright © 2015")] [assembly: AssemblyCopyright("Copyright © Titanium 2015-2017")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: InternalsVisibleTo("Titanium.Web.Proxy.UnitTests, PublicKey=" + [assembly: InternalsVisibleTo("Titanium.Web.Proxy.UnitTests, PublicKey=" +
......
...@@ -2,8 +2,10 @@ ...@@ -2,8 +2,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions; using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
...@@ -12,45 +14,44 @@ namespace Titanium.Web.Proxy ...@@ -12,45 +14,44 @@ namespace Titanium.Web.Proxy
{ {
public partial class ProxyServer public partial class ProxyServer
{ {
private async Task<bool> CheckAuthorization(StreamWriter clientStreamWriter, IEnumerable<HttpHeader> headers) private async Task<bool> CheckAuthorization(StreamWriter clientStreamWriter, SessionEventArgs session)
{ {
if (AuthenticateUserFunc == null) if (AuthenticateUserFunc == null)
{ {
return true; return true;
} }
var httpHeaders = headers as ICollection<HttpHeader> ?? headers.ToArray(); var httpHeaders = session.WebSession.Request.RequestHeaders.ToArray();
try try
{ {
if (httpHeaders.All(t => t.Name != "Proxy-Authorization")) var header = httpHeaders.FirstOrDefault(t => t.Name == "Proxy-Authorization");
if (header == null)
{ {
await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Required"); session.WebSession.Response = await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Required");
return false; return false;
} }
var header = httpHeaders.FirstOrDefault(t => t.Name == "Proxy-Authorization"); string headerValue = header.Value.Trim();
if (header == null) throw new NullReferenceException();
var headerValue = header.Value.Trim();
if (!headerValue.StartsWith("basic", StringComparison.CurrentCultureIgnoreCase)) if (!headerValue.StartsWith("basic", StringComparison.CurrentCultureIgnoreCase))
{ {
//Return not authorized //Return not authorized
await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid"); session.WebSession.Response = await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
return false; return false;
} }
headerValue = headerValue.Substring(5).Trim(); headerValue = headerValue.Substring(5).Trim();
var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(headerValue)); string decoded = Encoding.UTF8.GetString(Convert.FromBase64String(headerValue));
if (decoded.Contains(":") == false) if (decoded.Contains(":") == false)
{ {
//Return not authorized //Return not authorized
await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid"); session.WebSession.Response = await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
return false; return false;
} }
var username = decoded.Substring(0, decoded.IndexOf(':')); string username = decoded.Substring(0, decoded.IndexOf(':'));
var password = decoded.Substring(decoded.IndexOf(':') + 1); string password = decoded.Substring(decoded.IndexOf(':') + 1);
return await AuthenticateUserFunc(username, password); return await AuthenticateUserFunc(username, password);
} }
catch (Exception e) catch (Exception e)
...@@ -58,25 +59,25 @@ namespace Titanium.Web.Proxy ...@@ -58,25 +59,25 @@ namespace Titanium.Web.Proxy
ExceptionFunc(new ProxyAuthorizationException("Error whilst authorizing request", e, httpHeaders)); ExceptionFunc(new ProxyAuthorizationException("Error whilst authorizing request", e, httpHeaders));
//Return not authorized //Return not authorized
await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid"); session.WebSession.Response = await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
return false; return false;
} }
} }
private async Task SendAuthentication407Response(StreamWriter clientStreamWriter, string description) private async Task<Response> SendAuthentication407Response(StreamWriter clientStreamWriter, string description)
{ {
await WriteResponseStatus(HttpHeader.Version11, "407", description, clientStreamWriter);
var response = new Response var response = new Response
{ {
ResponseHeaders = new Dictionary<string, HttpHeader> HttpVersion = HttpHeader.Version11,
{ ResponseStatusCode = (int)HttpStatusCode.ProxyAuthenticationRequired,
{ "Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"") }, ResponseStatusDescription = description
{ "Proxy-Connection", new HttpHeader("Proxy-Connection", "close") }
}
}; };
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync(); response.ResponseHeaders.AddHeader("Proxy-Authenticate", $"Basic realm=\"{ProxyRealm}\"");
response.ResponseHeaders.AddHeader("Proxy-Connection", "close");
await WriteResponse(response, clientStreamWriter);
return response;
} }
} }
} }
...@@ -8,12 +8,17 @@ using System.Security.Cryptography.X509Certificates; ...@@ -8,12 +8,17 @@ using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
#if NET45
using Titanium.Web.Proxy.Helpers.WinHttp; using Titanium.Web.Proxy.Helpers.WinHttp;
#endif
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Network.Tcp; using Titanium.Web.Proxy.Network.Tcp;
#if NET45
using Titanium.Web.Proxy.Network.WinAuth.Security; using Titanium.Web.Proxy.Network.WinAuth.Security;
#endif
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
...@@ -22,6 +27,14 @@ namespace Titanium.Web.Proxy ...@@ -22,6 +27,14 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public partial class ProxyServer : IDisposable public partial class ProxyServer : IDisposable
{ {
#if NET45
internal static readonly string UriSchemeHttp = Uri.UriSchemeHttp;
internal static readonly string UriSchemeHttps = Uri.UriSchemeHttps;
#else
internal const string UriSchemeHttp = "http";
internal const string UriSchemeHttps = "https";
#endif
/// <summary> /// <summary>
/// Is the proxy currently running /// Is the proxy currently running
/// </summary> /// </summary>
...@@ -37,7 +50,9 @@ namespace Titanium.Web.Proxy ...@@ -37,7 +50,9 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
private Action<Exception> exceptionFunc; private Action<Exception> exceptionFunc;
#if NET45
private WinHttpWebProxyFinder systemProxyResolver; private WinHttpWebProxyFinder systemProxyResolver;
#endif
/// <summary> /// <summary>
/// Backing field for corresponding public property /// Backing field for corresponding public property
...@@ -52,13 +67,14 @@ namespace Titanium.Web.Proxy ...@@ -52,13 +67,14 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// Backing field for corresponding public property /// Backing field for corresponding public property
/// </summary> /// </summary>
internal int serverConnectionCount; private int serverConnectionCount;
/// <summary> /// <summary>
/// A object that creates tcp connection to server /// A object that creates tcp connection to server
/// </summary> /// </summary>
private TcpConnectionFactory tcpConnectionFactory { get; } private TcpConnectionFactory tcpConnectionFactory { get; }
#if NET45
/// <summary> /// <summary>
/// Manage system proxy settings /// Manage system proxy settings
/// </summary> /// </summary>
...@@ -67,9 +83,8 @@ namespace Titanium.Web.Proxy ...@@ -67,9 +83,8 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// Set firefox to use default system proxy /// Set firefox to use default system proxy
/// </summary> /// </summary>
private FireFoxProxySettingsManager firefoxProxySettingsManager private readonly FireFoxProxySettingsManager firefoxProxySettingsManager = new FireFoxProxySettingsManager();
= new FireFoxProxySettingsManager(); #endif
/// <summary> /// <summary>
/// Buffer size used throughout this proxy /// Buffer size used throughout this proxy
...@@ -174,6 +189,26 @@ namespace Titanium.Web.Proxy ...@@ -174,6 +189,26 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public event Func<object, SessionEventArgs, Task> BeforeResponse; public event Func<object, SessionEventArgs, Task> BeforeResponse;
/// <summary>
/// Intercept tunnel connect reques
/// </summary>
public event Func<object, TunnelConnectSessionEventArgs, Task> TunnelConnectRequest;
/// <summary>
/// Intercept tunnel connect response
/// </summary>
public event Func<object, TunnelConnectSessionEventArgs, Task> TunnelConnectResponse;
/// <summary>
/// Occurs when client connection count changed.
/// </summary>
public event EventHandler ClientConnectionCountChanged;
/// <summary>
/// Occurs when server connection count changed.
/// </summary>
public event EventHandler ServerConnectionCountChanged;
/// <summary> /// <summary>
/// External proxy for Http /// External proxy for Http
/// </summary> /// </summary>
...@@ -235,6 +270,10 @@ namespace Titanium.Web.Proxy ...@@ -235,6 +270,10 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public Func<string, string, Task<bool>> AuthenticateUserFunc { get; set; } public Func<string, string, Task<bool>> AuthenticateUserFunc { get; set; }
/// <summary>
/// Realm used during Proxy Basic Authentication
/// </summary>
public string ProxyRealm { get; set; } = "TitaniumProxy";
/// <summary> /// <summary>
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP requests /// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP requests
/// return the ExternalProxy object with valid credentials /// return the ExternalProxy object with valid credentials
...@@ -255,15 +294,13 @@ namespace Titanium.Web.Proxy ...@@ -255,15 +294,13 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// List of supported Ssl versions /// List of supported Ssl versions
/// </summary> /// </summary>
public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
| SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
/// <summary> /// <summary>
/// Total number of active client connections /// Total number of active client connections
/// </summary> /// </summary>
public int ClientConnectionCount => clientConnectionCount; public int ClientConnectionCount => clientConnectionCount;
/// <summary> /// <summary>
/// Total number of active server connections /// Total number of active server connections
/// </summary> /// </summary>
...@@ -289,10 +326,12 @@ namespace Titanium.Web.Proxy ...@@ -289,10 +326,12 @@ namespace Titanium.Web.Proxy
ProxyEndPoints = new List<ProxyEndPoint>(); ProxyEndPoints = new List<ProxyEndPoint>();
tcpConnectionFactory = new TcpConnectionFactory(); tcpConnectionFactory = new TcpConnectionFactory();
#if NET45
if (!RunTime.IsRunningOnMono) if (!RunTime.IsRunningOnMono)
{ {
systemProxySettingsManager = new SystemProxyManager(); systemProxySettingsManager = new SystemProxyManager();
} }
#endif
CertificateManager = new CertificateManager(ExceptionFunc); CertificateManager = new CertificateManager(ExceptionFunc);
if (rootCertificateName != null) if (rootCertificateName != null)
...@@ -312,8 +351,7 @@ namespace Titanium.Web.Proxy ...@@ -312,8 +351,7 @@ namespace Titanium.Web.Proxy
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
public void AddEndPoint(ProxyEndPoint endPoint) public void AddEndPoint(ProxyEndPoint endPoint)
{ {
if (ProxyEndPoints.Any(x => x.IpAddress.Equals(endPoint.IpAddress) if (ProxyEndPoints.Any(x => x.IpAddress.Equals(endPoint.IpAddress) && endPoint.Port != 0 && x.Port == endPoint.Port))
&& endPoint.Port != 0 && x.Port == endPoint.Port))
{ {
throw new Exception("Cannot add another endpoint to same port & ip address"); throw new Exception("Cannot add another endpoint to same port & ip address");
} }
...@@ -346,6 +384,7 @@ namespace Titanium.Web.Proxy ...@@ -346,6 +384,7 @@ namespace Titanium.Web.Proxy
} }
} }
#if NET45
/// <summary> /// <summary>
/// Set the given explicit end point as the default proxy server for current machine /// Set the given explicit end point as the default proxy server for current machine
/// </summary> /// </summary>
...@@ -412,7 +451,9 @@ namespace Titanium.Web.Proxy ...@@ -412,7 +451,9 @@ namespace Titanium.Web.Proxy
systemProxySettingsManager.SetProxy( systemProxySettingsManager.SetProxy(
Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Any) |
Equals(endPoint.IpAddress, IPAddress.Loopback) ? "127.0.0.1" : endPoint.IpAddress.ToString(), Equals(endPoint.IpAddress, IPAddress.Loopback)
? "127.0.0.1"
: endPoint.IpAddress.ToString(),
endPoint.Port, endPoint.Port,
protocolType); protocolType);
...@@ -489,6 +530,7 @@ namespace Titanium.Web.Proxy ...@@ -489,6 +530,7 @@ namespace Titanium.Web.Proxy
systemProxySettingsManager.DisableAllProxy(); systemProxySettingsManager.DisableAllProxy();
} }
#endif
/// <summary> /// <summary>
/// Start this proxy server /// Start this proxy server
...@@ -500,6 +542,7 @@ namespace Titanium.Web.Proxy ...@@ -500,6 +542,7 @@ namespace Titanium.Web.Proxy
throw new Exception("Proxy is already running."); throw new Exception("Proxy is already running.");
} }
#if NET45
//clear any system proxy settings which is pointing to our own endpoint //clear any system proxy settings which is pointing to our own endpoint
//due to non gracious proxy shutdown before //due to non gracious proxy shutdown before
if (systemProxySettingsManager != null) if (systemProxySettingsManager != null)
...@@ -535,6 +578,7 @@ namespace Titanium.Web.Proxy ...@@ -535,6 +578,7 @@ namespace Titanium.Web.Proxy
GetCustomUpStreamHttpProxyFunc = GetSystemUpStreamProxy; GetCustomUpStreamHttpProxyFunc = GetSystemUpStreamProxy;
GetCustomUpStreamHttpsProxyFunc = GetSystemUpStreamProxy; GetCustomUpStreamHttpsProxyFunc = GetSystemUpStreamProxy;
} }
#endif
foreach (var endPoint in ProxyEndPoints) foreach (var endPoint in ProxyEndPoints)
{ {
...@@ -543,11 +587,13 @@ namespace Titanium.Web.Proxy ...@@ -543,11 +587,13 @@ namespace Titanium.Web.Proxy
CertificateManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes); CertificateManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes);
#if NET45
if (!RunTime.IsRunningOnMono) if (!RunTime.IsRunningOnMono)
{ {
//clear orphaned windows auth states every 2 minutes //clear orphaned windows auth states every 2 minutes
WinAuthEndPoint.ClearIdleStates(2); WinAuthEndPoint.ClearIdleStates(2);
} }
#endif
proxyRunning = true; proxyRunning = true;
} }
...@@ -563,15 +609,17 @@ namespace Titanium.Web.Proxy ...@@ -563,15 +609,17 @@ namespace Titanium.Web.Proxy
throw new Exception("Proxy is not running."); throw new Exception("Proxy is not running.");
} }
#if NET45
if (!RunTime.IsRunningOnMono) if (!RunTime.IsRunningOnMono)
{ {
var setAsSystemProxy = ProxyEndPoints.OfType<ExplicitProxyEndPoint>().Any(x => x.IsSystemHttpProxy || x.IsSystemHttpsProxy); bool setAsSystemProxy = ProxyEndPoints.OfType<ExplicitProxyEndPoint>().Any(x => x.IsSystemHttpProxy || x.IsSystemHttpsProxy);
if (setAsSystemProxy) if (setAsSystemProxy)
{ {
systemProxySettingsManager.RestoreOriginalSettings(); systemProxySettingsManager.RestoreOriginalSettings();
} }
} }
#endif
foreach (var endPoint in ProxyEndPoints) foreach (var endPoint in ProxyEndPoints)
{ {
...@@ -598,6 +646,7 @@ namespace Titanium.Web.Proxy ...@@ -598,6 +646,7 @@ namespace Titanium.Web.Proxy
CertificateManager?.Dispose(); CertificateManager?.Dispose();
} }
#if NET45
/// <summary> /// <summary>
/// Listen on the given end point on local machine /// Listen on the given end point on local machine
/// </summary> /// </summary>
...@@ -611,6 +660,23 @@ namespace Titanium.Web.Proxy ...@@ -611,6 +660,23 @@ namespace Titanium.Web.Proxy
// accept clients asynchronously // accept clients asynchronously
endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint); endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
} }
#else
private async void Listen(ProxyEndPoint endPoint)
{
endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
endPoint.Listener.Start();
endPoint.Port = ((IPEndPoint)endPoint.Listener.LocalEndpoint).Port;
while (true)
{
TcpClient tcpClient = await endPoint.Listener.AcceptTcpClientAsync();
if (tcpClient != null)
Task.Run(async () => HandleClient(tcpClient, endPoint));
}
}
#endif
/// <summary> /// <summary>
/// Verifiy if its safe to set this end point as System proxy /// Verifiy if its safe to set this end point as System proxy
...@@ -618,7 +684,8 @@ namespace Titanium.Web.Proxy ...@@ -618,7 +684,8 @@ namespace Titanium.Web.Proxy
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
private void ValidateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint) private void ValidateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint)
{ {
if (endPoint == null) throw new ArgumentNullException(nameof(endPoint)); if (endPoint == null)
throw new ArgumentNullException(nameof(endPoint));
if (ProxyEndPoints.Contains(endPoint) == false) if (ProxyEndPoints.Contains(endPoint) == false)
{ {
throw new Exception("Cannot set endPoints not added to proxy as system proxy"); throw new Exception("Cannot set endPoints not added to proxy as system proxy");
...@@ -630,6 +697,7 @@ namespace Titanium.Web.Proxy ...@@ -630,6 +697,7 @@ namespace Titanium.Web.Proxy
} }
} }
#if NET45
/// <summary> /// <summary>
/// Gets the system up stream proxy. /// Gets the system up stream proxy.
/// </summary> /// </summary>
...@@ -640,7 +708,7 @@ namespace Titanium.Web.Proxy ...@@ -640,7 +708,7 @@ namespace Titanium.Web.Proxy
var proxy = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri); var proxy = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri);
return Task.FromResult(proxy); return Task.FromResult(proxy);
} }
#endif
private void EnsureRootCertificate() private void EnsureRootCertificate()
{ {
...@@ -655,6 +723,7 @@ namespace Titanium.Web.Proxy ...@@ -655,6 +723,7 @@ namespace Titanium.Web.Proxy
} }
} }
#if NET45
/// <summary> /// <summary>
/// When a connection is received from client act /// When a connection is received from client act
/// </summary> /// </summary>
...@@ -686,7 +755,18 @@ namespace Titanium.Web.Proxy ...@@ -686,7 +755,18 @@ namespace Titanium.Web.Proxy
{ {
Task.Run(async () => Task.Run(async () =>
{ {
Interlocked.Increment(ref clientConnectionCount); await HandleClient(tcpClient, endPoint);
});
}
// Get the listener that handles the client request.
endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
}
#endif
private async Task HandleClient(TcpClient tcpClient, ProxyEndPoint endPoint)
{
UpdateClientConnectionCount(true);
tcpClient.ReceiveTimeout = ConnectionTimeOutSeconds * 1000; tcpClient.ReceiveTimeout = ConnectionTimeOutSeconds * 1000;
tcpClient.SendTimeout = ConnectionTimeOutSeconds * 1000; tcpClient.SendTimeout = ConnectionTimeOutSeconds * 1000;
...@@ -704,7 +784,7 @@ namespace Titanium.Web.Proxy ...@@ -704,7 +784,7 @@ namespace Titanium.Web.Proxy
} }
finally finally
{ {
Interlocked.Decrement(ref clientConnectionCount); UpdateClientConnectionCount(false);
try try
{ {
...@@ -715,18 +795,13 @@ namespace Titanium.Web.Proxy ...@@ -715,18 +795,13 @@ namespace Titanium.Web.Proxy
//It helps to avoid eventual deterioration of performance due to TCP port exhaustion //It helps to avoid eventual deterioration of performance due to TCP port exhaustion
//due to default TCP CLOSE_WAIT timeout for 4 minutes //due to default TCP CLOSE_WAIT timeout for 4 minutes
tcpClient.LingerState = new LingerOption(true, 0); tcpClient.LingerState = new LingerOption(true, 0);
tcpClient.Close(); tcpClient.Dispose();
} }
} }
catch catch
{ {
} }
} }
});
}
// Get the listener that handles the client request.
endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
} }
/// <summary> /// <summary>
...@@ -736,8 +811,35 @@ namespace Titanium.Web.Proxy ...@@ -736,8 +811,35 @@ namespace Titanium.Web.Proxy
private void QuitListen(ProxyEndPoint endPoint) private void QuitListen(ProxyEndPoint endPoint)
{ {
endPoint.Listener.Stop(); endPoint.Listener.Stop();
endPoint.Listener.Server.Close();
endPoint.Listener.Server.Dispose(); endPoint.Listener.Server.Dispose();
} }
internal void UpdateClientConnectionCount(bool increment)
{
if (increment)
{
Interlocked.Increment(ref clientConnectionCount);
}
else
{
Interlocked.Decrement(ref clientConnectionCount);
}
ClientConnectionCountChanged?.Invoke(this, EventArgs.Empty);
}
internal void UpdateServerConnectionCount(bool increment)
{
if (increment)
{
Interlocked.Increment(ref serverConnectionCount);
}
else
{
Interlocked.Decrement(ref serverConnectionCount);
}
ServerConnectionCountChanged?.Invoke(this, EventArgs.Empty);
}
} }
} }
...@@ -6,6 +6,7 @@ using System.Net; ...@@ -6,6 +6,7 @@ using System.Net;
using System.Net.Security; using System.Net.Security;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Authentication; using System.Security.Authentication;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
...@@ -16,6 +17,7 @@ using Titanium.Web.Proxy.Http; ...@@ -16,6 +17,7 @@ using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.Tcp; using Titanium.Web.Proxy.Network.Tcp;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
using Titanium.Web.Proxy.Ssl;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
...@@ -33,44 +35,34 @@ namespace Titanium.Web.Proxy ...@@ -33,44 +35,34 @@ namespace Titanium.Web.Proxy
/// <returns></returns> /// <returns></returns>
private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient) private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient)
{ {
var disposed = false; bool disposed = false;
var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize); var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
var clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine }; var clientStreamWriter = new StreamWriter(clientStream)
{
NewLine = ProxyConstants.NewLine
};
Uri httpRemoteUri; Uri httpRemoteUri;
try try
{ {
//read the first line HTTP command //read the first line HTTP command
var httpCmd = await clientStreamReader.ReadLineAsync(); string httpCmd = await clientStreamReader.ReadLineAsync();
if (string.IsNullOrEmpty(httpCmd)) if (string.IsNullOrEmpty(httpCmd))
{ {
return; return;
} }
//break up the line into three components (method, remote URL & Http Version) string httpMethod;
var httpCmdSplit = httpCmd.Split(ProxyConstants.SpaceSplit, 3); string httpUrl;
Version version;
//Find the request Verb Request.ParseRequestLine(httpCmd, out httpMethod, out httpUrl, out version);
var httpVerb = httpCmdSplit[0].ToUpper();
httpRemoteUri = httpVerb == "CONNECT" ? new Uri("http://" + httpCmdSplit[1]) : new Uri(httpCmdSplit[1]); httpRemoteUri = httpMethod == "CONNECT" ? new Uri("http://" + httpUrl) : new Uri(httpUrl);
//parse the HTTP version
var version = HttpHeader.Version11;
if (httpCmdSplit.Length == 3)
{
var httpVersion = httpCmdSplit[2].Trim();
if (string.Equals(httpVersion, "HTTP/1.0", StringComparison.OrdinalIgnoreCase))
{
version = HttpHeader.Version10;
}
}
//filter out excluded host names //filter out excluded host names
bool excluded = false; bool excluded = false;
...@@ -85,29 +77,63 @@ namespace Titanium.Web.Proxy ...@@ -85,29 +77,63 @@ namespace Titanium.Web.Proxy
excluded = !endPoint.IncludedHttpsHostNameRegexList.Any(x => x.IsMatch(httpRemoteUri.Host)); excluded = !endPoint.IncludedHttpsHostNameRegexList.Any(x => x.IsMatch(httpRemoteUri.Host));
} }
List<HttpHeader> connectRequestHeaders = null; ConnectRequest connectRequest = null;
//Client wants to create a secure tcp tunnel (its a HTTPS request) //Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request)
if (httpVerb == "CONNECT" && !excluded if (httpMethod == "CONNECT")
&& endPoint.RemoteHttpsPorts.Contains(httpRemoteUri.Port))
{ {
httpRemoteUri = new Uri("https://" + httpCmdSplit[1]); connectRequest = new ConnectRequest
connectRequestHeaders = new List<HttpHeader>();
string tmpLine;
while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync()))
{ {
var header = tmpLine.Split(ProxyConstants.ColonSplit, 2); RequestUri = httpRemoteUri,
OriginalRequestUrl = httpUrl,
HttpVersion = version,
Method = httpMethod,
};
await HeaderParser.ReadHeaders(clientStreamReader, connectRequest.RequestHeaders);
var connectArgs = new TunnelConnectSessionEventArgs(endPoint);
connectArgs.WebSession.Request = connectRequest;
connectArgs.ProxyClient.TcpClient = tcpClient;
connectArgs.ProxyClient.ClientStream = clientStream;
connectArgs.ProxyClient.ClientStreamReader = clientStreamReader;
connectArgs.ProxyClient.ClientStreamWriter = clientStreamWriter;
var newHeader = new HttpHeader(header[0], header[1]); if (TunnelConnectRequest != null)
connectRequestHeaders.Add(newHeader); {
await TunnelConnectRequest.InvokeParallelAsync(this, connectArgs, ExceptionFunc);
} }
if (await CheckAuthorization(clientStreamWriter, connectRequestHeaders) == false) if (!excluded && await CheckAuthorization(clientStreamWriter, connectArgs) == false)
{
if (TunnelConnectResponse != null)
{ {
await TunnelConnectResponse.InvokeParallelAsync(this, connectArgs, ExceptionFunc);
}
return; return;
} }
await WriteConnectResponse(clientStreamWriter, version); //write back successfull CONNECT response
connectArgs.WebSession.Response = CreateConnectResponse(version);
await WriteResponse(connectArgs.WebSession.Response, clientStreamWriter);
var clientHelloInfo = await HttpsTools.GetClientHelloInfo(clientStream);
bool isClientHello = clientHelloInfo != null;
if (isClientHello)
{
connectRequest.ClientHelloInfo = clientHelloInfo;
}
if (TunnelConnectResponse != null)
{
connectArgs.IsHttpsConnect = isClientHello;
await TunnelConnectResponse.InvokeParallelAsync(this, connectArgs, ExceptionFunc);
}
if (!excluded && isClientHello)
{
httpRemoteUri = new Uri("https://" + httpUrl);
SslStream sslStream = null; SslStream sslStream = null;
...@@ -115,21 +141,22 @@ namespace Titanium.Web.Proxy ...@@ -115,21 +141,22 @@ namespace Titanium.Web.Proxy
{ {
sslStream = new SslStream(clientStream); sslStream = new SslStream(clientStream);
var certName = HttpHelper.GetWildCardDomainName(httpRemoteUri.Host); string certName = HttpHelper.GetWildCardDomainName(httpRemoteUri.Host);
var certificate = endPoint.GenericCertificate ?? var certificate = endPoint.GenericCertificate ?? CertificateManager.CreateCertificate(certName, false);
CertificateManager.CreateCertificate(certName, false);
//Successfully managed to authenticate the client using the fake certificate //Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false, await sslStream.AuthenticateAsServerAsync(certificate, false, SupportedSslProtocols, false);
SupportedSslProtocols, false);
//HTTPS server created - we can now decrypt the client's traffic //HTTPS server created - we can now decrypt the client's traffic
clientStream = new CustomBufferedStream(sslStream, BufferSize); clientStream = new CustomBufferedStream(sslStream, BufferSize);
clientStreamReader.Dispose(); clientStreamReader.Dispose();
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine }; clientStreamWriter = new StreamWriter(clientStream)
{
NewLine = ProxyConstants.NewLine
};
} }
catch catch
{ {
...@@ -141,27 +168,23 @@ namespace Titanium.Web.Proxy ...@@ -141,27 +168,23 @@ namespace Titanium.Web.Proxy
httpCmd = await clientStreamReader.ReadLineAsync(); httpCmd = await clientStreamReader.ReadLineAsync();
} }
//Sorry cannot do a HTTPS request decrypt to port 80 at this time //Sorry cannot do a HTTPS request decrypt to port 80 at this time
else if (httpVerb == "CONNECT") else
{ {
//Siphon out CONNECT request headers //create new connection
await clientStreamReader.ReadAndIgnoreAllLinesAsync(); using (var connection = await GetServerConnection(connectArgs))
{
//write back successfull CONNECT response await TcpHelper.SendRaw(clientStream, connection,
await WriteConnectResponse(clientStreamWriter, version); (buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); });
UpdateServerConnectionCount(false);
await TcpHelper.SendRaw(this, }
httpRemoteUri.Host, httpRemoteUri.Port,
null, version, null,
false,
clientStream, tcpConnectionFactory);
return; return;
} }
}
//Now create the request //Now create the request
disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter, disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
httpRemoteUri.Scheme == Uri.UriSchemeHttps ? httpRemoteUri.Host : null, endPoint, httpRemoteUri.Scheme == UriSchemeHttps ? httpRemoteUri.Host : null, endPoint, connectRequest);
connectRequestHeaders);
} }
catch (Exception e) catch (Exception e)
{ {
...@@ -194,29 +217,37 @@ namespace Titanium.Web.Proxy ...@@ -194,29 +217,37 @@ namespace Titanium.Web.Proxy
try try
{ {
if (endPoint.EnableSsl) if (endPoint.EnableSsl)
{
var clientSslHelloInfo = await HttpsTools.GetClientHelloInfo(clientStream);
if (clientSslHelloInfo != null)
{ {
var sslStream = new SslStream(clientStream); var sslStream = new SslStream(clientStream);
clientStream = new CustomBufferedStream(sslStream, BufferSize); clientStream = new CustomBufferedStream(sslStream, BufferSize);
//implement in future once SNI supported by SSL stream, for now use the same certificate string sniHostName = clientSslHelloInfo.Extensions.FirstOrDefault(x => x.Name == "server_name")?.Data;
var certificate = CertificateManager.CreateCertificate(endPoint.GenericCertificateName, false);
var certificate = CertificateManager.CreateCertificate(sniHostName ?? endPoint.GenericCertificateName, false);
//Successfully managed to authenticate the client using the fake certificate //Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false, await sslStream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls, false);
SslProtocols.Tls, false); }
//HTTPS server created - we can now decrypt the client's traffic //HTTPS server created - we can now decrypt the client's traffic
} }
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new StreamWriter(clientStream) { NewLine = ProxyConstants.NewLine }; clientStreamWriter = new StreamWriter(clientStream)
{
NewLine = ProxyConstants.NewLine
};
//now read the request line //now read the request line
var httpCmd = await clientStreamReader.ReadLineAsync(); string httpCmd = await clientStreamReader.ReadLineAsync();
//Now create the request //Now create the request
disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter, disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
endPoint.EnableSsl ? endPoint.GenericCertificateName : null, endPoint, null); endPoint.EnableSsl ? endPoint.GenericCertificateName : null, endPoint, null, true);
} }
finally finally
{ {
...@@ -239,11 +270,12 @@ namespace Titanium.Web.Proxy ...@@ -239,11 +270,12 @@ namespace Titanium.Web.Proxy
/// <param name="clientStreamWriter"></param> /// <param name="clientStreamWriter"></param>
/// <param name="httpsConnectHostname"></param> /// <param name="httpsConnectHostname"></param>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
/// <param name="connectHeaders"></param> /// <param name="connectRequest"></param>
/// <param name="isTransparentEndPoint"></param>
/// <returns></returns> /// <returns></returns>
private async Task<bool> HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream, private async Task<bool> HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsConnectHostname, CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsConnectHostname,
ProxyEndPoint endPoint, List<HttpHeader> connectHeaders) ProxyEndPoint endPoint, ConnectRequest connectRequest, bool isTransparentEndPoint = false)
{ {
bool disposed = false; bool disposed = false;
...@@ -258,86 +290,59 @@ namespace Titanium.Web.Proxy ...@@ -258,86 +290,59 @@ namespace Titanium.Web.Proxy
break; break;
} }
var args = new SessionEventArgs(BufferSize, HandleHttpSessionResponse) var args = new SessionEventArgs(BufferSize, endPoint, HandleHttpSessionResponse)
{ {
ProxyClient = { TcpClient = client }, ProxyClient = { TcpClient = client },
WebSession = { ConnectHeaders = connectHeaders } WebSession = { ConnectRequest = connectRequest }
}; };
args.WebSession.ProcessId = new Lazy<int>(() =>
{
var remoteEndPoint = (IPEndPoint)args.ProxyClient.TcpClient.Client.RemoteEndPoint;
//If client is localhost get the process id
if (NetworkHelper.IsLocalIpAddress(remoteEndPoint.Address))
{
return NetworkHelper.GetProcessIdFromPort(remoteEndPoint.Port, endPoint.IpV6Enabled);
}
//can't access process Id of remote request from remote machine
return -1;
});
try try
{ {
//break up the line into three components (method, remote URL & Http Version) string httpMethod;
var httpCmdSplit = httpCmd.Split(ProxyConstants.SpaceSplit, 3); string httpUrl;
Version version;
var httpMethod = httpCmdSplit[0]; Request.ParseRequestLine(httpCmd, out httpMethod, out httpUrl, out version);
//find the request HTTP version
var httpVersion = HttpHeader.Version11;
if (httpCmdSplit.Length == 3)
{
var httpVersionString = httpCmdSplit[2].Trim();
if (string.Equals(httpVersionString, "HTTP/1.0", StringComparison.OrdinalIgnoreCase))
{
httpVersion = HttpHeader.Version10;
}
}
//Read the request headers in to unique and non-unique header collections //Read the request headers in to unique and non-unique header collections
await HeaderParser.ReadHeaders(clientStreamReader, args.WebSession.Request.NonUniqueRequestHeaders, args.WebSession.Request.RequestHeaders); await HeaderParser.ReadHeaders(clientStreamReader, args.WebSession.Request.RequestHeaders);
var httpRemoteUri = new Uri(httpsConnectHostname == null var httpRemoteUri = new Uri(httpsConnectHostname == null
? httpCmdSplit[1] ? isTransparentEndPoint ? string.Concat("http://", args.WebSession.Request.Host, httpUrl) : httpUrl
: string.Concat("https://", args.WebSession.Request.Host ?? httpsConnectHostname, httpCmdSplit[1])); : string.Concat("https://", args.WebSession.Request.Host ?? httpsConnectHostname, httpUrl));
args.WebSession.Request.RequestUri = httpRemoteUri; args.WebSession.Request.RequestUri = httpRemoteUri;
args.WebSession.Request.OriginalRequestUrl = httpUrl;
args.WebSession.Request.Method = httpMethod.Trim().ToUpper(); args.WebSession.Request.Method = httpMethod;
args.WebSession.Request.HttpVersion = httpVersion; args.WebSession.Request.HttpVersion = version;
args.ProxyClient.ClientStream = clientStream; args.ProxyClient.ClientStream = clientStream;
args.ProxyClient.ClientStreamReader = clientStreamReader; args.ProxyClient.ClientStreamReader = clientStreamReader;
args.ProxyClient.ClientStreamWriter = clientStreamWriter; args.ProxyClient.ClientStreamWriter = clientStreamWriter;
//proxy authorization check //proxy authorization check
if (httpsConnectHostname == null && if (httpsConnectHostname == null && await CheckAuthorization(clientStreamWriter, args) == false)
await CheckAuthorization(clientStreamWriter,
args.WebSession.Request.RequestHeaders.Values) == false)
{ {
args.Dispose(); args.Dispose();
break; break;
} }
PrepareRequestHeaders(args.WebSession.Request.RequestHeaders, args.WebSession); PrepareRequestHeaders(args.WebSession.Request.RequestHeaders);
args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority; args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority;
#if NET45
//if win auth is enabled //if win auth is enabled
//we need a cache of request body //we need a cache of request body
//so that we can send it after authentication in WinAuthHandler.cs //so that we can send it after authentication in WinAuthHandler.cs
if (EnableWinAuth if (EnableWinAuth && !RunTime.IsRunningOnMono && args.WebSession.Request.HasBody)
&& !RunTime.IsRunningOnMono
&& args.WebSession.Request.HasBody)
{ {
await args.GetRequestBody(); await args.GetRequestBody();
} }
#endif
//If user requested interception do it //If user requested interception do it
if (BeforeRequest != null) if (BeforeRequest != null)
{ {
await BeforeRequest.InvokeParallelAsync(this, args); await BeforeRequest.InvokeParallelAsync(this, args, ExceptionFunc);
} }
if (args.WebSession.Request.CancelRequest) if (args.WebSession.Request.CancelRequest)
...@@ -346,31 +351,70 @@ namespace Titanium.Web.Proxy ...@@ -346,31 +351,70 @@ namespace Titanium.Web.Proxy
break; break;
} }
//if upgrading to websocket then relay the requet without reading the contents
if (args.WebSession.Request.UpgradeToWebSocket)
{
await TcpHelper.SendRaw(this,
httpRemoteUri.Host, httpRemoteUri.Port,
httpCmd, httpVersion, args.WebSession.Request.RequestHeaders, args.IsHttps,
clientStream, tcpConnectionFactory, connection);
args.Dispose();
break;
}
if (connection == null) if (connection == null)
{ {
connection = await GetServerConnection(args); connection = await GetServerConnection(args);
} }
//create a new connection if hostname changes //create a new connection if hostname changes
else if (!connection.HostName.Equals(args.WebSession.Request.RequestUri.Host, else if (!connection.HostName.Equals(args.WebSession.Request.RequestUri.Host, StringComparison.OrdinalIgnoreCase))
StringComparison.OrdinalIgnoreCase))
{ {
connection.Dispose(); connection.Dispose();
Interlocked.Decrement(ref serverConnectionCount); UpdateServerConnectionCount(false);
connection = await GetServerConnection(args); connection = await GetServerConnection(args);
} }
//if upgrading to websocket then relay the requet without reading the contents
if (args.WebSession.Request.UpgradeToWebSocket)
{
//prepare the prefix content
var requestHeaders = args.WebSession.Request.RequestHeaders;
using (var ms = new MemoryStream())
using (var writer = new StreamWriter(ms, Encoding.ASCII) { NewLine = ProxyConstants.NewLine })
{
writer.WriteLine(httpCmd);
if (requestHeaders != null)
{
foreach (string header in requestHeaders.Select(t => t.ToString()))
{
writer.WriteLine(header);
}
}
writer.WriteLine();
writer.Flush();
var data = ms.ToArray();
await connection.Stream.WriteAsync(data, 0, data.Length);
}
string httpStatus = await connection.StreamReader.ReadLineAsync();
Version responseVersion;
int responseStatusCode;
string responseStatusDescription;
Response.ParseResponseLine(httpStatus, out responseVersion, out responseStatusCode, out responseStatusDescription);
args.WebSession.Response.HttpVersion = responseVersion;
args.WebSession.Response.ResponseStatusCode = responseStatusCode;
args.WebSession.Response.ResponseStatusDescription = responseStatusDescription;
await HeaderParser.ReadHeaders(connection.StreamReader, args.WebSession.Response.ResponseHeaders);
await WriteResponse(args.WebSession.Response, clientStreamWriter);
//If user requested call back then do it
if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked)
{
await BeforeResponse.InvokeParallelAsync(this, args, ExceptionFunc);
}
await TcpHelper.SendRaw(clientStream, connection,
(buffer, offset, count) => { args.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); });
args.Dispose();
break;
}
//construct the web request that we are going to issue on behalf of the client. //construct the web request that we are going to issue on behalf of the client.
disposed = await HandleHttpSessionRequestInternal(connection, args, false); disposed = await HandleHttpSessionRequestInternal(connection, args, false);
...@@ -415,8 +459,7 @@ namespace Titanium.Web.Proxy ...@@ -415,8 +459,7 @@ namespace Titanium.Web.Proxy
/// <param name="args"></param> /// <param name="args"></param>
/// <param name="closeConnection"></param> /// <param name="closeConnection"></param>
/// <returns></returns> /// <returns></returns>
private async Task<bool> HandleHttpSessionRequestInternal(TcpConnection connection, private async Task<bool> HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args, bool closeConnection)
SessionEventArgs args, bool closeConnection)
{ {
bool disposed = false; bool disposed = false;
bool keepAlive = false; bool keepAlive = false;
...@@ -438,14 +481,12 @@ namespace Titanium.Web.Proxy ...@@ -438,14 +481,12 @@ namespace Titanium.Web.Proxy
{ {
if (args.WebSession.Request.Is100Continue) if (args.WebSession.Request.Is100Continue)
{ {
await WriteResponseStatus(args.WebSession.Response.HttpVersion, "100", await WriteResponseStatus(args.WebSession.Response.HttpVersion, (int)HttpStatusCode.Continue, "Continue", args.ProxyClient.ClientStreamWriter);
"Continue", args.ProxyClient.ClientStreamWriter);
await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
} }
else if (args.WebSession.Request.ExpectationFailed) else if (args.WebSession.Request.ExpectationFailed)
{ {
await WriteResponseStatus(args.WebSession.Response.HttpVersion, "417", await WriteResponseStatus(args.WebSession.Response.HttpVersion, (int)HttpStatusCode.ExpectationFailed, "Expectation Failed", args.ProxyClient.ClientStreamWriter);
"Expectation Failed", args.ProxyClient.ClientStreamWriter);
await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
} }
} }
...@@ -465,8 +506,10 @@ namespace Titanium.Web.Proxy ...@@ -465,8 +506,10 @@ namespace Titanium.Web.Proxy
{ {
if (args.WebSession.Request.ContentEncoding != null) if (args.WebSession.Request.ContentEncoding != null)
{ {
args.WebSession.Request.RequestBody = await GetCompressedResponseBody(args.WebSession.Request.ContentEncoding, args.WebSession.Request.RequestBody); args.WebSession.Request.RequestBody =
await GetCompressedResponseBody(args.WebSession.Request.ContentEncoding, args.WebSession.Request.RequestBody);
} }
//chunked send is not supported as of now //chunked send is not supported as of now
args.WebSession.Request.ContentLength = args.WebSession.Request.RequestBody.Length; args.WebSession.Request.ContentLength = args.WebSession.Request.RequestBody.Length;
...@@ -520,7 +563,8 @@ namespace Titanium.Web.Proxy ...@@ -520,7 +563,8 @@ namespace Titanium.Web.Proxy
if (!disposed && !keepAlive) if (!disposed && !keepAlive)
{ {
//dispose //dispose
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args.WebSession.ServerConnection); Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter,
args.WebSession.ServerConnection);
} }
} }
...@@ -532,24 +576,23 @@ namespace Titanium.Web.Proxy ...@@ -532,24 +576,23 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
/// <param name="args"></param> /// <param name="args"></param>
/// <returns></returns> /// <returns></returns>
private async Task<TcpConnection> GetServerConnection( private async Task<TcpConnection> GetServerConnection(SessionEventArgs args)
SessionEventArgs args)
{ {
ExternalProxy customUpStreamHttpProxy = null; ExternalProxy customUpStreamHttpProxy = null;
ExternalProxy customUpStreamHttpsProxy = null; ExternalProxy customUpStreamHttpsProxy = null;
if (args.WebSession.Request.RequestUri.Scheme == "http") if (args.WebSession.Request.IsHttps)
{ {
if (GetCustomUpStreamHttpProxyFunc != null) if (GetCustomUpStreamHttpsProxyFunc != null)
{ {
customUpStreamHttpProxy = await GetCustomUpStreamHttpProxyFunc(args); customUpStreamHttpsProxy = await GetCustomUpStreamHttpsProxyFunc(args);
} }
} }
else else
{ {
if (GetCustomUpStreamHttpsProxyFunc != null) if (GetCustomUpStreamHttpProxyFunc != null)
{ {
customUpStreamHttpsProxy = await GetCustomUpStreamHttpsProxyFunc(args); customUpStreamHttpProxy = await GetCustomUpStreamHttpProxyFunc(args);
} }
} }
...@@ -569,29 +612,29 @@ namespace Titanium.Web.Proxy ...@@ -569,29 +612,29 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// Write successfull CONNECT response to client /// Write successfull CONNECT response to client
/// </summary> /// </summary>
/// <param name="clientStreamWriter"></param>
/// <param name="httpVersion"></param> /// <param name="httpVersion"></param>
/// <returns></returns> /// <returns></returns>
private async Task WriteConnectResponse(StreamWriter clientStreamWriter, Version httpVersion) private ConnectResponse CreateConnectResponse(Version httpVersion)
{
var response = new ConnectResponse
{ {
await clientStreamWriter.WriteLineAsync( HttpVersion = httpVersion,
$"HTTP/{httpVersion.Major}.{httpVersion.Minor} 200 Connection established"); ResponseStatusCode = (int)HttpStatusCode.OK,
await clientStreamWriter.WriteLineAsync($"Timestamp: {DateTime.Now}"); ResponseStatusDescription = "Connection established"
await clientStreamWriter.WriteLineAsync(); };
await clientStreamWriter.FlushAsync();
response.ResponseHeaders.AddHeader("Timestamp", DateTime.Now.ToString());
return response;
} }
/// <summary> /// <summary>
/// prepare the request headers so that we can avoid encodings not parsable by this proxy /// prepare the request headers so that we can avoid encodings not parsable by this proxy
/// </summary> /// </summary>
/// <param name="requestHeaders"></param> /// <param name="requestHeaders"></param>
/// <param name="webRequest"></param> private void PrepareRequestHeaders(HeaderCollection requestHeaders)
private void PrepareRequestHeaders(Dictionary<string, HttpHeader> requestHeaders, HttpWebClient webRequest)
{ {
foreach (var headerItem in requestHeaders) foreach (var header in requestHeaders)
{ {
var header = headerItem.Value;
switch (header.Name.ToLower()) switch (header.Name.ToLower())
{ {
//these are the only encoding this proxy can read //these are the only encoding this proxy can read
...@@ -602,7 +645,6 @@ namespace Titanium.Web.Proxy ...@@ -602,7 +645,6 @@ namespace Titanium.Web.Proxy
} }
FixProxyHeaders(requestHeaders); FixProxyHeaders(requestHeaders);
webRequest.Request.RequestHeaders = requestHeaders;
} }
/// <summary> /// <summary>
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Compression; using Titanium.Web.Proxy.Compression;
...@@ -31,25 +32,29 @@ namespace Titanium.Web.Proxy ...@@ -31,25 +32,29 @@ namespace Titanium.Web.Proxy
//read response & headers from server //read response & headers from server
await args.WebSession.ReceiveResponse(); await args.WebSession.ReceiveResponse();
var response = args.WebSession.Response;
#if NET45
//check for windows authentication //check for windows authentication
if(EnableWinAuth if (EnableWinAuth
&& !RunTime.IsRunningOnMono && !RunTime.IsRunningOnMono
&& args.WebSession.Response.ResponseStatusCode == "401") && response.ResponseStatusCode == (int)HttpStatusCode.Unauthorized)
{ {
var disposed = await Handle401UnAuthorized(args); bool disposed = await Handle401UnAuthorized(args);
if(disposed) if (disposed)
{ {
return true; return true;
} }
} }
#endif
args.ReRequest = false; args.ReRequest = false;
//If user requested call back then do it //If user requested call back then do it
if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked) if (BeforeResponse != null && !response.ResponseLocked)
{ {
await BeforeResponse.InvokeParallelAsync(this, args); await BeforeResponse.InvokeParallelAsync(this, args, ExceptionFunc);
} }
//if user requested to send request again //if user requested to send request again
...@@ -58,72 +63,58 @@ namespace Titanium.Web.Proxy ...@@ -58,72 +63,58 @@ namespace Titanium.Web.Proxy
{ {
//clear current response //clear current response
await args.ClearResponse(); await args.ClearResponse();
var disposed = await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args, false); bool disposed = await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args, false);
return disposed; return disposed;
} }
args.WebSession.Response.ResponseLocked = true; response.ResponseLocked = true;
//Write back to client 100-conitinue response if that's what server returned //Write back to client 100-conitinue response if that's what server returned
if (args.WebSession.Response.Is100Continue) if (response.Is100Continue)
{ {
await WriteResponseStatus(args.WebSession.Response.HttpVersion, "100", await WriteResponseStatus(response.HttpVersion, (int)HttpStatusCode.Continue, "Continue", args.ProxyClient.ClientStreamWriter);
"Continue", args.ProxyClient.ClientStreamWriter);
await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
} }
else if (args.WebSession.Response.ExpectationFailed) else if (response.ExpectationFailed)
{ {
await WriteResponseStatus(args.WebSession.Response.HttpVersion, "417", await WriteResponseStatus(response.HttpVersion, (int)HttpStatusCode.ExpectationFailed, "Expectation Failed", args.ProxyClient.ClientStreamWriter);
"Expectation Failed", args.ProxyClient.ClientStreamWriter);
await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
} }
//Write back response status to client //Write back response status to client
await WriteResponseStatus(args.WebSession.Response.HttpVersion, args.WebSession.Response.ResponseStatusCode, await WriteResponseStatus(response.HttpVersion, response.ResponseStatusCode, response.ResponseStatusDescription, args.ProxyClient.ClientStreamWriter);
args.WebSession.Response.ResponseStatusDescription, args.ProxyClient.ClientStreamWriter);
if (args.WebSession.Response.ResponseBodyRead) if (response.ResponseBodyRead)
{ {
var isChunked = args.WebSession.Response.IsChunked; bool isChunked = response.IsChunked;
var contentEncoding = args.WebSession.Response.ContentEncoding; string contentEncoding = response.ContentEncoding;
if (contentEncoding != null) if (contentEncoding != null)
{ {
args.WebSession.Response.ResponseBody = await GetCompressedResponseBody(contentEncoding, args.WebSession.Response.ResponseBody); response.ResponseBody = await GetCompressedResponseBody(contentEncoding, response.ResponseBody);
if (isChunked == false) if (isChunked == false)
{ {
args.WebSession.Response.ContentLength = args.WebSession.Response.ResponseBody.Length; response.ContentLength = response.ResponseBody.Length;
} }
else else
{ {
args.WebSession.Response.ContentLength = -1; response.ContentLength = -1;
} }
} }
await WriteResponseHeaders(args.ProxyClient.ClientStreamWriter, args.WebSession.Response); await WriteResponseHeaders(args.ProxyClient.ClientStreamWriter, response);
await args.ProxyClient.ClientStream.WriteResponseBody(args.WebSession.Response.ResponseBody, isChunked); await args.ProxyClient.ClientStream.WriteResponseBody(response.ResponseBody, isChunked);
} }
else else
{ {
await WriteResponseHeaders(args.ProxyClient.ClientStreamWriter, args.WebSession.Response); await WriteResponseHeaders(args.ProxyClient.ClientStreamWriter, response);
//Write body only if response is chunked or content length >0 //Write body if exists
//Is none are true then check if connection:close header exist, if so write response until server or client terminates the connection if (response.HasBody)
if (args.WebSession.Response.IsChunked || args.WebSession.Response.ContentLength > 0
|| !args.WebSession.Response.ResponseKeepAlive)
{
await args.WebSession.ServerConnection.StreamReader
.WriteResponseBody(BufferSize, args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked,
args.WebSession.Response.ContentLength);
}
//write response if connection:keep-alive header exist and when version is http/1.0
//Because in Http 1.0 server can return a response without content-length (expectation being client would read until end of stream)
else if (args.WebSession.Response.ResponseKeepAlive && args.WebSession.Response.HttpVersion.Minor == 0)
{ {
await args.WebSession.ServerConnection.StreamReader await args.WebSession.ServerConnection.StreamReader.WriteResponseBody(BufferSize, args.ProxyClient.ClientStream,
.WriteResponseBody(BufferSize, args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked, response.IsChunked, response.ContentLength);
args.WebSession.Response.ContentLength);
} }
} }
...@@ -132,8 +123,8 @@ namespace Titanium.Web.Proxy ...@@ -132,8 +123,8 @@ namespace Titanium.Web.Proxy
catch (Exception e) catch (Exception e)
{ {
ExceptionFunc(new ProxyHttpException("Error occured whilst handling session response", e, args)); ExceptionFunc(new ProxyHttpException("Error occured whilst handling session response", e, args));
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter,
args.ProxyClient.ClientStreamWriter, args.WebSession.ServerConnection); args.WebSession.ServerConnection);
return true; return true;
} }
...@@ -154,6 +145,19 @@ namespace Titanium.Web.Proxy ...@@ -154,6 +145,19 @@ namespace Titanium.Web.Proxy
return await compressor.Compress(responseBodyStream); return await compressor.Compress(responseBodyStream);
} }
/// <summary>
/// Writes the response.
/// </summary>
/// <param name="response"></param>
/// <param name="responseWriter"></param>
/// <param name="flush"></param>
/// <returns></returns>
private async Task WriteResponse(Response response, StreamWriter responseWriter, bool flush = true)
{
await WriteResponseStatus(response.HttpVersion, response.ResponseStatusCode, response.ResponseStatusDescription, responseWriter);
await WriteResponseHeaders(responseWriter, response, flush);
}
/// <summary> /// <summary>
/// Write response status /// Write response status
/// </summary> /// </summary>
...@@ -162,8 +166,7 @@ namespace Titanium.Web.Proxy ...@@ -162,8 +166,7 @@ namespace Titanium.Web.Proxy
/// <param name="description"></param> /// <param name="description"></param>
/// <param name="responseWriter"></param> /// <param name="responseWriter"></param>
/// <returns></returns> /// <returns></returns>
private async Task WriteResponseStatus(Version version, string code, string description, private async Task WriteResponseStatus(Version version, int code, string description, StreamWriter responseWriter)
StreamWriter responseWriter)
{ {
await responseWriter.WriteLineAsync($"HTTP/{version.Major}.{version.Minor} {code} {description}"); await responseWriter.WriteLineAsync($"HTTP/{version.Major}.{version.Minor} {code} {description}");
} }
...@@ -173,54 +176,37 @@ namespace Titanium.Web.Proxy ...@@ -173,54 +176,37 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
/// <param name="responseWriter"></param> /// <param name="responseWriter"></param>
/// <param name="response"></param> /// <param name="response"></param>
/// <param name="flush"></param>
/// <returns></returns> /// <returns></returns>
private async Task WriteResponseHeaders(StreamWriter responseWriter, Response response) private async Task WriteResponseHeaders(StreamWriter responseWriter, Response response, bool flush = true)
{ {
FixProxyHeaders(response.ResponseHeaders); FixProxyHeaders(response.ResponseHeaders);
foreach (var header in response.ResponseHeaders) foreach (var header in response.ResponseHeaders)
{
await header.Value.WriteToStream(responseWriter);
}
//write non unique request headers
foreach (var headerItem in response.NonUniqueResponseHeaders)
{
var headers = headerItem.Value;
foreach (var header in headers)
{ {
await header.WriteToStream(responseWriter); await header.WriteToStream(responseWriter);
} }
}
await responseWriter.WriteLineAsync(); await responseWriter.WriteLineAsync();
if (flush)
{
await responseWriter.FlushAsync(); await responseWriter.FlushAsync();
} }
}
/// <summary> /// <summary>
/// Fix proxy specific headers /// Fix proxy specific headers
/// </summary> /// </summary>
/// <param name="headers"></param> /// <param name="headers"></param>
private void FixProxyHeaders(Dictionary<string, HttpHeader> headers) private void FixProxyHeaders(HeaderCollection headers)
{ {
//If proxy-connection close was returned inform to close the connection //If proxy-connection close was returned inform to close the connection
var hasProxyHeader = headers.ContainsKey("proxy-connection"); string proxyHeader = headers.GetHeaderValueOrNull("proxy-connection");
var hasConnectionheader = headers.ContainsKey("connection"); headers.RemoveHeader("proxy-connection");
if (hasProxyHeader) if (proxyHeader != null)
{ {
var proxyHeader = headers["proxy-connection"]; headers.SetOrAddHeaderValue("connection", proxyHeader);
if (hasConnectionheader == false)
{
headers.Add("connection", new HttpHeader("connection", proxyHeader.Value));
}
else
{
var connectionHeader = headers["connection"];
connectionHeader.Value = proxyHeader.Value;
}
headers.Remove("proxy-connection");
} }
} }
...@@ -231,12 +217,8 @@ namespace Titanium.Web.Proxy ...@@ -231,12 +217,8 @@ namespace Titanium.Web.Proxy
/// <param name="clientStreamReader"></param> /// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param> /// <param name="clientStreamWriter"></param>
/// <param name="serverConnection"></param> /// <param name="serverConnection"></param>
private void Dispose(Stream clientStream, private void Dispose(Stream clientStream, CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, TcpConnection serverConnection)
CustomBinaryReader clientStreamReader,
StreamWriter clientStreamWriter,
TcpConnection serverConnection)
{ {
clientStream?.Close();
clientStream?.Dispose(); clientStream?.Dispose();
clientStreamReader?.Dispose(); clientStreamReader?.Dispose();
...@@ -245,7 +227,7 @@ namespace Titanium.Web.Proxy ...@@ -245,7 +227,7 @@ namespace Titanium.Web.Proxy
if (serverConnection != null) if (serverConnection != null)
{ {
serverConnection.Dispose(); serverConnection.Dispose();
Interlocked.Decrement(ref serverConnectionCount); UpdateServerConnectionCount(false);
} }
} }
} }
......
...@@ -16,8 +16,7 @@ namespace Titanium.Web.Proxy.Shared ...@@ -16,8 +16,7 @@ namespace Titanium.Web.Proxy.Shared
internal static readonly byte[] NewLineBytes = Encoding.ASCII.GetBytes(NewLine); internal static readonly byte[] NewLineBytes = Encoding.ASCII.GetBytes(NewLine);
internal static readonly byte[] ChunkEnd = internal static readonly byte[] ChunkEnd = Encoding.ASCII.GetBytes(0.ToString("x2") + NewLine + NewLine);
Encoding.ASCII.GetBytes(0.ToString("x2") + NewLine + NewLine);
internal const string NewLine = "\r\n"; internal const string NewLine = "\r\n";
} }
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Titanium.Web.Proxy.Ssl
{
public class ClientHelloInfo
{
private static readonly string[] compressions = {
"null",
"DEFLATE"
};
public int MajorVersion { get; set; }
public int MinorVersion { get; set; }
public byte[] Random { get; set; }
public DateTime Time
{
get
{
DateTime time = DateTime.MinValue;
if (Random.Length > 3)
{
time = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)
.AddSeconds(((uint)Random[3] << 24) + ((uint)Random[2] << 16) + ((uint)Random[1] << 8) + (uint)Random[0]).ToLocalTime();
}
return time;
}
}
public byte[] SessionId { get; set; }
public int[] Ciphers { get; set; }
public byte[] CompressionData { get; set; }
public List<SslExtension> Extensions { get; set; }
private static string SslVersionToString(int major, int minor)
{
string str = "Unknown";
if (major == 3 && minor == 3)
str = "TLS/1.2";
else if (major == 3 && minor == 2)
str = "TLS/1.1";
else if (major == 3 && minor == 1)
str = "TLS/1.0";
else if (major == 3 && minor == 0)
str = "SSL/3.0";
else if (major == 2 && minor == 0)
str = "SSL/2.0";
return $"{major}.{minor} ({str})";
}
/// <summary>
/// Returns a <see cref="System.String" /> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String" /> that represents this instance.
/// </returns>
public override string ToString()
{
var sb = new StringBuilder();
sb.AppendLine("A SSLv3-compatible ClientHello handshake was found. Titanium extracted the parameters below.");
sb.AppendLine();
sb.AppendLine($"Version: {SslVersionToString(MajorVersion, MinorVersion)}");
sb.AppendLine($"Random: {string.Join(" ", Random.Select(x => x.ToString("X2")))}");
sb.AppendLine($"\"Time\": {Time}");
sb.AppendLine($"SessionID: {string.Join(" ", SessionId.Select(x => x.ToString("X2")))}");
if (Extensions != null)
{
sb.AppendLine("Extensions:");
foreach (var extension in Extensions)
{
sb.AppendLine($"{extension.Name}: {extension.Data}");
}
}
if (CompressionData.Length > 0)
{
int id = CompressionData[0];
string compression = null;
compression = compressions.Length > id ? compressions[id] : $"unknown [0x{id:X2}]";
sb.AppendLine($"Compression: {compression}");
}
if (Ciphers.Length > 0)
{
sb.AppendLine($"Ciphers:");
foreach (int cipher in Ciphers)
{
string cipherStr;
if (!SslCiphers.Ciphers.TryGetValue(cipher, out cipherStr))
{
cipherStr = $"unknown";
}
sb.AppendLine($"[0x{cipher:X4}] {cipherStr}");
}
}
return sb.ToString();
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Ssl
{
static class SslCiphers
{
public static readonly Dictionary<int, string> Ciphers = new Dictionary<int, string>
{
{ 0x0000, "TLS_NULL_WITH_NULL_NULL" },
{ 0x0001, "TLS_RSA_WITH_NULL_MD5" },
{ 0x0002, "TLS_RSA_WITH_NULL_SHA" },
{ 0x0003, "TLS_RSA_EXPORT_WITH_RC4_40_MD5" },
{ 0x0004, "TLS_RSA_WITH_RC4_128_MD5" },
{ 0x0005, "TLS_RSA_WITH_RC4_128_SHA" },
{ 0x0006, "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5" },
{ 0x0007, "TLS_RSA_WITH_IDEA_CBC_SHA" },
{ 0x0008, "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x0009, "TLS_RSA_WITH_DES_CBC_SHA" },
{ 0x000A, "TLS_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0x000B, "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x000C, "TLS_DH_DSS_WITH_DES_CBC_SHA" },
{ 0x000D, "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA" },
{ 0x000E, "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x000F, "TLS_DH_RSA_WITH_DES_CBC_SHA" },
{ 0x0010, "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0x0011, "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x0012, "TLS_DHE_DSS_WITH_DES_CBC_SHA" },
{ 0x0013, "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA" },
{ 0x0014, "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x0015, "TLS_DHE_RSA_WITH_DES_CBC_SHA" },
{ 0x0016, "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0x0017, "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5" },
{ 0x0018, "TLS_DH_anon_WITH_RC4_128_MD5" },
{ 0x0019, "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x001A, "TLS_DH_anon_WITH_DES_CBC_SHA" },
{ 0x001B, "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA" },
{ 0x001E, "TLS_KRB5_WITH_DES_CBC_SHA" },
{ 0x001F, "TLS_KRB5_WITH_3DES_EDE_CBC_SHA" },
{ 0x0020, "TLS_KRB5_WITH_RC4_128_SHA" },
{ 0x0021, "TLS_KRB5_WITH_IDEA_CBC_SHA" },
{ 0x0022, "TLS_KRB5_WITH_DES_CBC_MD5" },
{ 0x0023, "TLS_KRB5_WITH_3DES_EDE_CBC_MD5" },
{ 0x0024, "TLS_KRB5_WITH_RC4_128_MD5" },
{ 0x0025, "TLS_KRB5_WITH_IDEA_CBC_MD5" },
{ 0x0026, "TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA" },
{ 0x0027, "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA" },
{ 0x0028, "TLS_KRB5_EXPORT_WITH_RC4_40_SHA" },
{ 0x0029, "TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5" },
{ 0x002A, "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5" },
{ 0x002B, "TLS_KRB5_EXPORT_WITH_RC4_40_MD5" },
{ 0x002C, "TLS_PSK_WITH_NULL_SHA" },
{ 0x002D, "TLS_DHE_PSK_WITH_NULL_SHA" },
{ 0x002E, "TLS_RSA_PSK_WITH_NULL_SHA" },
{ 0x002F, "TLS_RSA_WITH_AES_128_CBC_SHA" },
{ 0x0030, "TLS_DH_DSS_WITH_AES_128_CBC_SHA" },
{ 0x0031, "TLS_DH_RSA_WITH_AES_128_CBC_SHA" },
{ 0x0032, "TLS_DHE_DSS_WITH_AES_128_CBC_SHA" },
{ 0x0033, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA" },
{ 0x0034, "TLS_DH_anon_WITH_AES_128_CBC_SHA" },
{ 0x0035, "TLS_RSA_WITH_AES_256_CBC_SHA" },
{ 0x0036, "TLS_DH_DSS_WITH_AES_256_CBC_SHA" },
{ 0x0037, "TLS_DH_RSA_WITH_AES_256_CBC_SHA" },
{ 0x0038, "TLS_DHE_DSS_WITH_AES_256_CBC_SHA" },
{ 0x0039, "TLS_DHE_RSA_WITH_AES_256_CBC_SHA" },
{ 0x003A, "TLS_DH_anon_WITH_AES_256_CBC_SHA" },
{ 0x003B, "TLS_RSA_WITH_NULL_SHA256" },
{ 0x003C, "TLS_RSA_WITH_AES_128_CBC_SHA256" },
{ 0x003D, "TLS_RSA_WITH_AES_256_CBC_SHA256" },
{ 0x003E, "TLS_DH_DSS_WITH_AES_128_CBC_SHA256" },
{ 0x003F, "TLS_DH_RSA_WITH_AES_128_CBC_SHA256" },
{ 0x0040, "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256" },
{ 0x0041, "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0042, "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0043, "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0044, "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0045, "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0046, "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0067, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256" },
{ 0x0068, "TLS_DH_DSS_WITH_AES_256_CBC_SHA256" },
{ 0x0069, "TLS_DH_RSA_WITH_AES_256_CBC_SHA256" },
{ 0x006A, "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256" },
{ 0x006B, "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256" },
{ 0x006C, "TLS_DH_anon_WITH_AES_128_CBC_SHA256" },
{ 0x006D, "TLS_DH_anon_WITH_AES_256_CBC_SHA256" },
{ 0x0084, "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x0085, "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x0086, "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x0087, "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x0088, "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x0089, "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x008A, "TLS_PSK_WITH_RC4_128_SHA" },
{ 0x008B, "TLS_PSK_WITH_3DES_EDE_CBC_SHA" },
{ 0x008C, "TLS_PSK_WITH_AES_128_CBC_SHA" },
{ 0x008D, "TLS_PSK_WITH_AES_256_CBC_SHA" },
{ 0x008E, "TLS_DHE_PSK_WITH_RC4_128_SHA" },
{ 0x008F, "TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA" },
{ 0x0090, "TLS_DHE_PSK_WITH_AES_128_CBC_SHA" },
{ 0x0091, "TLS_DHE_PSK_WITH_AES_256_CBC_SHA" },
{ 0x0092, "TLS_RSA_PSK_WITH_RC4_128_SHA" },
{ 0x0093, "TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA" },
{ 0x0094, "TLS_RSA_PSK_WITH_AES_128_CBC_SHA" },
{ 0x0095, "TLS_RSA_PSK_WITH_AES_256_CBC_SHA" },
{ 0x0096, "TLS_RSA_WITH_SEED_CBC_SHA" },
{ 0x0097, "TLS_DH_DSS_WITH_SEED_CBC_SHA" },
{ 0x0098, "TLS_DH_RSA_WITH_SEED_CBC_SHA" },
{ 0x0099, "TLS_DHE_DSS_WITH_SEED_CBC_SHA" },
{ 0x009A, "TLS_DHE_RSA_WITH_SEED_CBC_SHA" },
{ 0x009B, "TLS_DH_anon_WITH_SEED_CBC_SHA" },
{ 0x009C, "TLS_RSA_WITH_AES_128_GCM_SHA256" },
{ 0x009D, "TLS_RSA_WITH_AES_256_GCM_SHA384" },
{ 0x009E, "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256" },
{ 0x009F, "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384" },
{ 0x00A0, "TLS_DH_RSA_WITH_AES_128_GCM_SHA256" },
{ 0x00A1, "TLS_DH_RSA_WITH_AES_256_GCM_SHA384" },
{ 0x00A2, "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256" },
{ 0x00A3, "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384" },
{ 0x00A4, "TLS_DH_DSS_WITH_AES_128_GCM_SHA256" },
{ 0x00A5, "TLS_DH_DSS_WITH_AES_256_GCM_SHA384" },
{ 0x00A6, "TLS_DH_anon_WITH_AES_128_GCM_SHA256" },
{ 0x00A7, "TLS_DH_anon_WITH_AES_256_GCM_SHA384" },
{ 0x00A8, "TLS_PSK_WITH_AES_128_GCM_SHA256" },
{ 0x00A9, "TLS_PSK_WITH_AES_256_GCM_SHA384" },
{ 0x00AA, "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256" },
{ 0x00AB, "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384" },
{ 0x00AC, "TLS_RSA_PSK_WITH_AES_128_GCM_SHA256" },
{ 0x00AD, "TLS_RSA_PSK_WITH_AES_256_GCM_SHA384" },
{ 0x00AE, "TLS_PSK_WITH_AES_128_CBC_SHA256" },
{ 0x00AF, "TLS_PSK_WITH_AES_256_CBC_SHA384" },
{ 0x00B0, "TLS_PSK_WITH_NULL_SHA256" },
{ 0x00B1, "TLS_PSK_WITH_NULL_SHA384" },
{ 0x00B2, "TLS_DHE_PSK_WITH_AES_128_CBC_SHA256" },
{ 0x00B3, "TLS_DHE_PSK_WITH_AES_256_CBC_SHA384" },
{ 0x00B4, "TLS_DHE_PSK_WITH_NULL_SHA256" },
{ 0x00B5, "TLS_DHE_PSK_WITH_NULL_SHA384" },
{ 0x00B6, "TLS_RSA_PSK_WITH_AES_128_CBC_SHA256" },
{ 0x00B7, "TLS_RSA_PSK_WITH_AES_256_CBC_SHA384" },
{ 0x00B8, "TLS_RSA_PSK_WITH_NULL_SHA256" },
{ 0x00B9, "TLS_RSA_PSK_WITH_NULL_SHA384" },
{ 0x00BA, "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00BB, "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00BC, "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00BD, "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00BE, "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00BF, "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00C0, "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00C1, "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00C2, "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00C3, "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00C4, "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00C5, "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00FF, "TLS_EMPTY_RENEGOTIATION_INFO_SCSV" },
{ 0x5600, "TLS_FALLBACK_SCSV" },
{ 0xC001, "TLS_ECDH_ECDSA_WITH_NULL_SHA" },
{ 0xC002, "TLS_ECDH_ECDSA_WITH_RC4_128_SHA" },
{ 0xC003, "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC004, "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA" },
{ 0xC005, "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA" },
{ 0xC006, "TLS_ECDHE_ECDSA_WITH_NULL_SHA" },
{ 0xC007, "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA" },
{ 0xC008, "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC009, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA" },
{ 0xC00A, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA" },
{ 0xC00B, "TLS_ECDH_RSA_WITH_NULL_SHA" },
{ 0xC00C, "TLS_ECDH_RSA_WITH_RC4_128_SHA" },
{ 0xC00D, "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC00E, "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA" },
{ 0xC00F, "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA" },
{ 0xC010, "TLS_ECDHE_RSA_WITH_NULL_SHA" },
{ 0xC011, "TLS_ECDHE_RSA_WITH_RC4_128_SHA" },
{ 0xC012, "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC013, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" },
{ 0xC014, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" },
{ 0xC015, "TLS_ECDH_anon_WITH_NULL_SHA" },
{ 0xC016, "TLS_ECDH_anon_WITH_RC4_128_SHA" },
{ 0xC017, "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA" },
{ 0xC018, "TLS_ECDH_anon_WITH_AES_128_CBC_SHA" },
{ 0xC019, "TLS_ECDH_anon_WITH_AES_256_CBC_SHA" },
{ 0xC01A, "TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC01B, "TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC01C, "TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA" },
{ 0xC01D, "TLS_SRP_SHA_WITH_AES_128_CBC_SHA" },
{ 0xC01E, "TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA" },
{ 0xC01F, "TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA" },
{ 0xC020, "TLS_SRP_SHA_WITH_AES_256_CBC_SHA" },
{ 0xC021, "TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA" },
{ 0xC022, "TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA" },
{ 0xC023, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256" },
{ 0xC024, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384" },
{ 0xC025, "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256" },
{ 0xC026, "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384" },
{ 0xC027, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" },
{ 0xC028, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384" },
{ 0xC029, "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256" },
{ 0xC02A, "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384" },
{ 0xC02B, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" },
{ 0xC02C, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" },
{ 0xC02D, "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256" },
{ 0xC02E, "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384" },
{ 0xC02F, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" },
{ 0xC030, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" },
{ 0xC031, "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256" },
{ 0xC032, "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384" },
{ 0xC033, "TLS_ECDHE_PSK_WITH_RC4_128_SHA" },
{ 0xC034, "TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA" },
{ 0xC035, "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA" },
{ 0xC036, "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA" },
{ 0xC037, "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256" },
{ 0xC038, "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384" },
{ 0xC039, "TLS_ECDHE_PSK_WITH_NULL_SHA" },
{ 0xC03A, "TLS_ECDHE_PSK_WITH_NULL_SHA256" },
{ 0xC03B, "TLS_ECDHE_PSK_WITH_NULL_SHA384" },
{ 0xC03C, "TLS_RSA_WITH_ARIA_128_CBC_SHA256" },
{ 0xC03D, "TLS_RSA_WITH_ARIA_256_CBC_SHA384" },
{ 0xC03E, "TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256" },
{ 0xC03F, "TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384" },
{ 0xC040, "TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256" },
{ 0xC041, "TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384" },
{ 0xC042, "TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256" },
{ 0xC043, "TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384" },
{ 0xC044, "TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256" },
{ 0xC045, "TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384" },
{ 0xC046, "TLS_DH_anon_WITH_ARIA_128_CBC_SHA256" },
{ 0xC047, "TLS_DH_anon_WITH_ARIA_256_CBC_SHA384" },
{ 0xC048, "TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256" },
{ 0xC049, "TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384" },
{ 0xC04A, "TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256" },
{ 0xC04B, "TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384" },
{ 0xC04C, "TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256" },
{ 0xC04D, "TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384" },
{ 0xC04E, "TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256" },
{ 0xC04F, "TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384" },
{ 0xC050, "TLS_RSA_WITH_ARIA_128_GCM_SHA256" },
{ 0xC051, "TLS_RSA_WITH_ARIA_256_GCM_SHA384" },
{ 0xC052, "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256" },
{ 0xC053, "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384" },
{ 0xC054, "TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256" },
{ 0xC055, "TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384" },
{ 0xC056, "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256" },
{ 0xC057, "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384" },
{ 0xC058, "TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256" },
{ 0xC059, "TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384" },
{ 0xC05A, "TLS_DH_anon_WITH_ARIA_128_GCM_SHA256" },
{ 0xC05B, "TLS_DH_anon_WITH_ARIA_256_GCM_SHA384" },
{ 0xC05C, "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256" },
{ 0xC05D, "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384" },
{ 0xC05E, "TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256" },
{ 0xC05F, "TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384" },
{ 0xC060, "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256" },
{ 0xC061, "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384" },
{ 0xC062, "TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256" },
{ 0xC063, "TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384" },
{ 0xC064, "TLS_PSK_WITH_ARIA_128_CBC_SHA256" },
{ 0xC065, "TLS_PSK_WITH_ARIA_256_CBC_SHA384" },
{ 0xC066, "TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256" },
{ 0xC067, "TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384" },
{ 0xC068, "TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256" },
{ 0xC069, "TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384" },
{ 0xC06A, "TLS_PSK_WITH_ARIA_128_GCM_SHA256" },
{ 0xC06B, "TLS_PSK_WITH_ARIA_256_GCM_SHA384" },
{ 0xC06C, "TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256" },
{ 0xC06D, "TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384" },
{ 0xC06E, "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256" },
{ 0xC06F, "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384" },
{ 0xC070, "TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256" },
{ 0xC071, "TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384" },
{ 0xC072, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC073, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC074, "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC075, "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC076, "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC077, "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC078, "TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC079, "TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC07A, "TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC07B, "TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC07C, "TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC07D, "TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC07E, "TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC07F, "TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC080, "TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC081, "TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC082, "TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC083, "TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC084, "TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC085, "TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC086, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC087, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC088, "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC089, "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC08A, "TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC08B, "TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC08C, "TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC08D, "TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC08E, "TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC08F, "TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC090, "TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC091, "TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC092, "TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC093, "TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC094, "TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC095, "TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC096, "TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC097, "TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC098, "TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC099, "TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC09A, "TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC09B, "TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC09C, "TLS_RSA_WITH_AES_128_CCM" },
{ 0xC09D, "TLS_RSA_WITH_AES_256_CCM" },
{ 0xC09E, "TLS_DHE_RSA_WITH_AES_128_CCM" },
{ 0xC09F, "TLS_DHE_RSA_WITH_AES_256_CCM" },
{ 0xC0A0, "TLS_RSA_WITH_AES_128_CCM_8" },
{ 0xC0A1, "TLS_RSA_WITH_AES_256_CCM_8" },
{ 0xC0A2, "TLS_DHE_RSA_WITH_AES_128_CCM_8" },
{ 0xC0A3, "TLS_DHE_RSA_WITH_AES_256_CCM_8" },
{ 0xC0A4, "TLS_PSK_WITH_AES_128_CCM" },
{ 0xC0A5, "TLS_PSK_WITH_AES_256_CCM" },
{ 0xC0A6, "TLS_DHE_PSK_WITH_AES_128_CCM" },
{ 0xC0A7, "TLS_DHE_PSK_WITH_AES_256_CCM" },
{ 0xC0A8, "TLS_PSK_WITH_AES_128_CCM_8" },
{ 0xC0A9, "TLS_PSK_WITH_AES_256_CCM_8" },
{ 0xC0AA, "TLS_PSK_DHE_WITH_AES_128_CCM_8" },
{ 0xC0AB, "TLS_PSK_DHE_WITH_AES_256_CCM_8" },
{ 0xC0AC, "TLS_ECDHE_ECDSA_WITH_AES_128_CCM" },
{ 0xC0AD, "TLS_ECDHE_ECDSA_WITH_AES_256_CCM" },
{ 0xC0AE, "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8" },
{ 0xC0AF, "TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8" },
{ 0xCCA8, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCA9, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCAA, "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCAB, "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCAC, "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCAD, "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCAE, "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256" },
};
}
}
namespace Titanium.Web.Proxy.Ssl
{
public class SslExtension
{
public int Value { get; set; }
public string Name { get; set; }
public string Data { get; set; }
public SslExtension(int value, string name, string data)
{
Value = value;
Name = name;
Data = data;
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Ssl
{
internal class SslExtensions
{
internal static SslExtension GetExtension(int value, byte[] data)
{
string name = GetExtensionName(value);
string dataStr = GetExtensionData(value, data);
return new SslExtension(value, name, dataStr);
}
private static string GetExtensionData(int value, byte[] data)
{
//https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
switch (value)
{
case 0:
var stringBuilder = new StringBuilder();
int index = 2;
while (index < data.Length)
{
int nameType = data[index];
int count = (data[index + 1] << 8) + data[index + 2];
string str = Encoding.ASCII.GetString(data, index + 3, count);
if (nameType == 0)
{
stringBuilder.AppendFormat("{0}{1}", stringBuilder.Length > 1 ? "; " : string.Empty, str);
}
index += 3 + count;
}
return stringBuilder.ToString();
case 5:
if (data.Length == 5 && data[0] == 1 && data[1] == 0 && data[2] == 0 && data[3] == 0 && data[4] == 0)
{
return "OCSP - Implicit Responder";
}
return ByteArrayToString(data);
case 10:
return GetSupportedGroup(data);
case 11:
return GetEcPointFormats(data);
case 13:
return GetSignatureAlgorithms(data);
case 16:
return GetApplicationLayerProtocolNegotiation(data);
case 35655:
return $"{data.Length} bytes";
default:
return ByteArrayToString(data);
}
}
private static string GetSupportedGroup(byte[] data)
{
//https://datatracker.ietf.org/doc/draft-ietf-tls-rfc4492bis/?include_text=1
List<string> list = new List<string>();
if (data.Length < 2)
{
return string.Empty;
}
int i = 2;
while (i < data.Length - 1)
{
int namedCurve = (data[i] << 8) + data[i + 1];
switch (namedCurve)
{
case 1:
list.Add("sect163k1 [0x1]"); //deprecated
break;
case 2:
list.Add("sect163r1 [0x2]"); //deprecated
break;
case 3:
list.Add("sect163r2 [0x3]"); //deprecated
break;
case 4:
list.Add("sect193r1 [0x4]"); //deprecated
break;
case 5:
list.Add("sect193r2 [0x5]"); //deprecated
break;
case 6:
list.Add("sect233k1 [0x6]"); //deprecated
break;
case 7:
list.Add("sect233r1 [0x7]"); //deprecated
break;
case 8:
list.Add("sect239k1 [0x8]"); //deprecated
break;
case 9:
list.Add("sect283k1 [0x9]"); //deprecated
break;
case 10:
list.Add("sect283r1 [0xA]"); //deprecated
break;
case 11:
list.Add("sect409k1 [0xB]"); //deprecated
break;
case 12:
list.Add("sect409r1 [0xC]"); //deprecated
break;
case 13:
list.Add("sect571k1 [0xD]"); //deprecated
break;
case 14:
list.Add("sect571r1 [0xE]"); //deprecated
break;
case 15:
list.Add("secp160k1 [0xF]"); //deprecated
break;
case 16:
list.Add("secp160r1 [0x10]"); //deprecated
break;
case 17:
list.Add("secp160r2 [0x11]"); //deprecated
break;
case 18:
list.Add("secp192k1 [0x12]"); //deprecated
break;
case 19:
list.Add("secp192r1 [0x13]"); //deprecated
break;
case 20:
list.Add("secp224k1 [0x14]"); //deprecated
break;
case 21:
list.Add("secp224r1 [0x15]"); //deprecated
break;
case 22:
list.Add("secp256k1 [0x16]"); //deprecated
break;
case 23:
list.Add("secp256r1 [0x17]");
break;
case 24:
list.Add("secp384r1 [0x18]");
break;
case 25:
list.Add("secp521r1 [0x19]");
break;
case 26:
list.Add("brainpoolP256r1 [0x1A]");
break;
case 27:
list.Add("brainpoolP384r1 [0x1B]");
break;
case 28:
list.Add("brainpoolP512r1 [0x1C]");
break;
case 29:
list.Add("x25519 [0x1D]");
break;
case 30:
list.Add("x448 [0x1E]");
break;
case 256:
list.Add("ffdhe2048 [0x0100]");
break;
case 257:
list.Add("ffdhe3072 [0x0101]");
break;
case 258:
list.Add("ffdhe4096 [0x0102]");
break;
case 259:
list.Add("ffdhe6144 [0x0103]");
break;
case 260:
list.Add("ffdhe8192 [0x0104]");
break;
case 65281:
list.Add("arbitrary_explicit_prime_curves [0xFF01]"); //deprecated
break;
case 65282:
list.Add("arbitrary_explicit_char2_curves [0xFF02]"); //deprecated
break;
default:
list.Add($"unknown [0x{namedCurve:X4}]");
break;
}
i += 2;
}
return string.Join(", ", list.ToArray());
}
private static string GetEcPointFormats(byte[] data)
{
List<string> list = new List<string>();
if (data.Length < 1)
{
return string.Empty;
}
int i = 1;
while (i < data.Length)
{
switch (data[i])
{
case 0:
list.Add("uncompressed [0x0]");
break;
case 1:
list.Add("ansiX962_compressed_prime [0x1]");
break;
case 2:
list.Add("ansiX962_compressed_char2 [0x2]");
break;
default:
list.Add($"unknown [0x{data[i]:X2}]");
break;
}
i += 2;
}
return string.Join(", ", list.ToArray());
}
private static string GetSignatureAlgorithms(byte[] data)
{
// https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml
int num = (data[0] << 8) + data[1];
var sb = new StringBuilder();
int index = 2;
while (index < num + 2)
{
switch (data[index])
{
case 0:
sb.Append("none");
break;
case 1:
sb.Append("md5");
break;
case 2:
sb.Append("sha1");
break;
case 3:
sb.Append("sha224");
break;
case 4:
sb.Append("sha256");
break;
case 5:
sb.Append("sha384");
break;
case 6:
sb.Append("sha512");
break;
case 8:
sb.Append("Intrinsic");
break;
default:
sb.AppendFormat("Unknown[0x{0:X2}]", data[index]);
break;
}
sb.AppendFormat("_");
switch (data[index + 1])
{
case 0:
sb.Append("anonymous");
break;
case 1:
sb.Append("rsa");
break;
case 2:
sb.Append("dsa");
break;
case 3:
sb.Append("ecdsa");
break;
case 7:
sb.Append("ed25519");
break;
case 8:
sb.Append("ed448");
break;
default:
sb.AppendFormat("Unknown[0x{0:X2}]", data[index + 1]);
break;
}
sb.AppendFormat(", ");
index += 2;
}
if (sb.Length > 1)
sb.Length -= 2;
return sb.ToString();
}
private static string GetApplicationLayerProtocolNegotiation(byte[] data)
{
List<string> stringList = new List<string>();
int index = 2;
while (index < data.Length)
{
int count = data[index];
stringList.Add(Encoding.ASCII.GetString(data, index + 1, count));
index += 1 + count;
}
return string.Join(", ", stringList.ToArray());
}
private static string GetExtensionName(int value)
{
//https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
switch (value)
{
case 0:
return "server_name";
case 1:
return "max_fragment_length";
case 2:
return "client_certificate_url";
case 3:
return "trusted_ca_keys";
case 4:
return "truncated_hmac";
case 5:
return "status_request";
case 6:
return "user_mapping";
case 7:
return "client_authz";
case 8:
return "server_authz";
case 9:
return "cert_type";
case 10:
return "supported_groups"; // renamed from "elliptic_curves"
case 11:
return "ec_point_formats";
case 12:
return "srp";
case 13:
return "signature_algorithms";
case 14:
return "use_srtp";
case 15:
return "heartbeat";
case 16:
return "ALPN"; // application_layer_protocol_negotiation
case 17:
return "status_request_v2";
case 18:
return "signed_certificate_timestamp";
case 19:
return "client_certificate_type";
case 20:
return "server_certificate_type";
case 21:
return "padding";
case 22:
return "encrypt_then_mac";
case 23:
return "extended_master_secret";
case 24:
return "token_binding"; // TEMPORARY - registered 2016-02-04, extension registered 2017-01-12, expires 2018-02-04
case 25:
return "cached_info";
case 35:
return "SessionTicket TLS";
case 13172:
return "next_protocol_negotiation";
case 30031:
case 30032:
return "channel_id"; // Google
case 35655:
return "draft-agl-tls-padding";
case 65281:
return "renegotiation_info";
default:
return "unknown";
}
}
private static string ByteArrayToString(byte[] data)
{
return string.Join(" ", data.Select(x => x.ToString("X2")));
}
}
}
using System.Collections.Generic;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Ssl
{
class HttpsTools
{
public static async Task<bool> IsClientHello(CustomBufferedStream clientStream)
{
var clientHello = await GetClientHelloInfo(clientStream);
return clientHello != null;
}
public static async Task<ClientHelloInfo> GetClientHelloInfo(CustomBufferedStream clientStream)
{
//detects the HTTPS ClientHello message as it is described in the following url:
//https://stackoverflow.com/questions/3897883/how-to-detect-an-incoming-ssl-https-handshake-ssl-wire-format
int recordType = await clientStream.PeekByteAsync(0);
if (recordType == 0x80)
{
var peekStream = new CustomBufferedPeekStream(clientStream, 1);
//SSL 2
int length = peekStream.ReadByte();
if (length < 9)
{
// Message body too short.
return null;
}
if (peekStream.ReadByte() != 0x01)
{
// should be ClientHello
return null;
}
int majorVersion = clientStream.ReadByte();
int minorVersion = clientStream.ReadByte();
return new ClientHelloInfo();
}
else if (recordType == 0x16)
{
var peekStream = new CustomBufferedPeekStream(clientStream, 1);
//should contain at least 43 bytes
// 2 version + 2 length + 1 type + 3 length(?) + 2 version + 32 random + 1 sessionid length
if (!await peekStream.EnsureBufferLength(43))
{
return null;
}
//SSL 3.0 or TLS 1.0, 1.1 and 1.2
int majorVersion = peekStream.ReadByte();
int minorVersion = peekStream.ReadByte();
int length = peekStream.ReadInt16();
if (peekStream.ReadByte() != 0x01)
{
// should be ClientHello
return null;
}
length = peekStream.ReadInt24();
majorVersion = peekStream.ReadByte();
minorVersion = peekStream.ReadByte();
byte[] random = peekStream.ReadBytes(32);
length = peekStream.ReadByte();
// sessionid + 2 ciphersData length
if (!await peekStream.EnsureBufferLength(length + 2))
{
return null;
}
byte[] sessionId = peekStream.ReadBytes(length);
length = peekStream.ReadInt16();
// ciphersData + compressionData length
if (!await peekStream.EnsureBufferLength(length + 1))
{
return null;
}
byte[] ciphersData = peekStream.ReadBytes(length);
int[] ciphers = new int[ciphersData.Length / 2];
for (int i = 0; i < ciphers.Length; i++)
{
ciphers[i] = (ciphersData[2 * i] << 8) + ciphersData[2 * i + 1];
}
length = peekStream.ReadByte();
if (length < 1)
{
return null;
}
// compressionData
if (!await peekStream.EnsureBufferLength(length))
{
return null;
}
byte[] compressionData = peekStream.ReadBytes(length);
List<SslExtension> extensions = null;
if (majorVersion > 3 || majorVersion == 3 && minorVersion >= 1)
{
if (await peekStream.EnsureBufferLength(2))
{
length = peekStream.ReadInt16();
if (await peekStream.EnsureBufferLength(length))
{
extensions = new List<SslExtension>();
while (peekStream.Available > 3)
{
int id = peekStream.ReadInt16();
length = peekStream.ReadInt16();
byte[] data = peekStream.ReadBytes(length);
extensions.Add(SslExtensions.GetExtension(id, data));
}
}
}
}
var clientHelloInfo = new ClientHelloInfo
{
MajorVersion = majorVersion,
MinorVersion = minorVersion,
Random = random,
SessionId = sessionId,
Ciphers = ciphers,
CompressionData = compressionData,
Extensions = extensions,
};
return clientHelloInfo;
}
return null;
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.5</TargetFramework>
<RootNamespace>Titanium.Web.Proxy</RootNamespace>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Properties\AssemblyInfo.cs" />
<Compile Remove="Helpers\WinHttp\NativeMethods.WinHttp.cs" />
<Compile Remove="Helpers\WinHttp\WinHttpHandle.cs" />
<Compile Remove="Helpers\WinHttp\WinHttpWebProxyFinder.cs" />
<Compile Remove="Helpers\NativeMethods.SystemProxy.cs" />
<Compile Remove="Helpers\NativeMethods.Tcp.cs" />
<Compile Remove="Helpers\Firefox.cs" />
<Compile Remove="Helpers\ProxyInfo.cs" />
<Compile Remove="Helpers\RunTime.cs" />
<Compile Remove="Helpers\SystemProxy.cs" />
<Compile Remove="Network\Certificate\WinCertificateMaker.cs" />
<Compile Remove="Network\Tcp\TcpRow.cs" />
<Compile Remove="Network\Tcp\TcpTable.cs" />
<Compile Remove="Network\WinAuth\Security\Common.cs" />
<Compile Remove="Network\WinAuth\Security\LittleEndian.cs" />
<Compile Remove="Network\WinAuth\Security\Message.cs" />
<Compile Remove="Network\WinAuth\Security\State.cs" />
<Compile Remove="Network\WinAuth\Security\WinAuthEndPoint.cs" />
<Compile Remove="Network\WinAuth\WinAuthHandler.cs" />
<Compile Remove="WinAuthHandler.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.8.1.2" />
<PackageReference Include="System.Net.NameResolution" Version="4.3.0" />
<PackageReference Include="System.Net.Security" Version="4.3.1" />
<PackageReference Include="System.Runtime.Serialization.Formatters" Version="4.3.0" />
<PackageReference Include="System.Security.SecureString" Version="4.3.0" />
</ItemGroup>
</Project>
\ No newline at end of file
...@@ -51,17 +51,16 @@ ...@@ -51,17 +51,16 @@
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Net" />
<Reference Include="System.Configuration" /> <Reference Include="System.Configuration" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Net" />
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="CertificateHandler.cs" />
<Compile Include="Compression\CompressionFactory.cs" /> <Compile Include="Compression\CompressionFactory.cs" />
<Compile Include="Compression\DeflateCompression.cs" /> <Compile Include="Compression\DeflateCompression.cs" />
<Compile Include="Compression\GZipCompression.cs" /> <Compile Include="Compression\GZipCompression.cs" />
...@@ -73,64 +72,81 @@ ...@@ -73,64 +72,81 @@
<Compile Include="Decompression\IDecompression.cs" /> <Compile Include="Decompression\IDecompression.cs" />
<Compile Include="EventArguments\CertificateSelectionEventArgs.cs" /> <Compile Include="EventArguments\CertificateSelectionEventArgs.cs" />
<Compile Include="EventArguments\CertificateValidationEventArgs.cs" /> <Compile Include="EventArguments\CertificateValidationEventArgs.cs" />
<Compile Include="EventArguments\DataEventArgs.cs" />
<Compile Include="EventArguments\SessionEventArgs.cs" />
<Compile Include="EventArguments\TunnelConnectEventArgs.cs" />
<Compile Include="Exceptions\BodyNotFoundException.cs" />
<Compile Include="Exceptions\ProxyAuthorizationException.cs" />
<Compile Include="Exceptions\ProxyException.cs" />
<Compile Include="Exceptions\ProxyHttpException.cs" />
<Compile Include="Extensions\ByteArrayExtensions.cs" /> <Compile Include="Extensions\ByteArrayExtensions.cs" />
<Compile Include="Extensions\DotNetStandardExtensions.cs" />
<Compile Include="Extensions\FuncExtensions.cs" /> <Compile Include="Extensions\FuncExtensions.cs" />
<Compile Include="Helpers\HttpHelper.cs" /> <Compile Include="Extensions\HttpWebRequestExtensions.cs" />
<Compile Include="Extensions\HttpWebResponseExtensions.cs" />
<Compile Include="Extensions\StreamExtensions.cs" />
<Compile Include="Extensions\StringExtensions.cs" /> <Compile Include="Extensions\StringExtensions.cs" />
<Compile Include="Extensions\TcpExtensions.cs" />
<Compile Include="Helpers\BufferPool.cs" /> <Compile Include="Helpers\BufferPool.cs" />
<Compile Include="Helpers\CustomBinaryReader.cs" />
<Compile Include="Helpers\CustomBufferedPeekStream.cs" />
<Compile Include="Helpers\CustomBufferedStream.cs" /> <Compile Include="Helpers\CustomBufferedStream.cs" />
<Compile Include="Helpers\ProxyInfo.cs" /> <Compile Include="Helpers\Firefox.cs" />
<Compile Include="Helpers\WinHttp\NativeMethods.WinHttp.cs" /> <Compile Include="Helpers\HttpHelper.cs" />
<Compile Include="Helpers\Network.cs" /> <Compile Include="Helpers\Network.cs" />
<Compile Include="Helpers\RunTime.cs" /> <Compile Include="Helpers\Tcp.cs" />
<Compile Include="Helpers\WinHttp\WinHttpHandle.cs" /> <Compile Include="Ssl\ClientHelloInfo.cs" />
<Compile Include="Helpers\WinHttp\WinHttpWebProxyFinder.cs" /> <Compile Include="Http\ConnectRequest.cs" />
<Compile Include="Http\ConnectResponse.cs" />
<Compile Include="Http\HeaderCollection.cs" />
<Compile Include="Http\HeaderParser.cs" /> <Compile Include="Http\HeaderParser.cs" />
<Compile Include="Ssl\SslCiphers.cs" />
<Compile Include="Ssl\SslExtension.cs" />
<Compile Include="Ssl\SslExtensions.cs" />
<Compile Include="Ssl\SslTools.cs" />
<Compile Include="Http\HttpWebClient.cs" />
<Compile Include="Http\Request.cs" />
<Compile Include="Http\Response.cs" />
<Compile Include="Http\Responses\GenericResponse.cs" /> <Compile Include="Http\Responses\GenericResponse.cs" />
<Compile Include="Http\Responses\OkResponse.cs" />
<Compile Include="Http\Responses\RedirectResponse.cs" />
<Compile Include="Models\EndPoint.cs" />
<Compile Include="Models\ExternalProxy.cs" />
<Compile Include="Models\HttpHeader.cs" />
<Compile Include="Network\CachedCertificate.cs" /> <Compile Include="Network\CachedCertificate.cs" />
<Compile Include="Network\Certificate\WinCertificateMaker.cs" />
<Compile Include="Network\Certificate\BCCertificateMaker.cs" /> <Compile Include="Network\Certificate\BCCertificateMaker.cs" />
<Compile Include="Network\Certificate\ICertificateMaker.cs" /> <Compile Include="Network\Certificate\ICertificateMaker.cs" />
<Compile Include="Network\ProxyClient.cs" /> <Compile Include="Network\Certificate\WinCertificateMaker.cs" />
<Compile Include="Exceptions\BodyNotFoundException.cs" />
<Compile Include="Exceptions\ProxyAuthorizationException.cs" />
<Compile Include="Exceptions\ProxyException.cs" />
<Compile Include="Exceptions\ProxyHttpException.cs" />
<Compile Include="Extensions\HttpWebResponseExtensions.cs" />
<Compile Include="Extensions\HttpWebRequestExtensions.cs" />
<Compile Include="Network\CertificateManager.cs" /> <Compile Include="Network\CertificateManager.cs" />
<Compile Include="Helpers\Firefox.cs" /> <Compile Include="Network\ProxyClient.cs" />
<Compile Include="Helpers\SystemProxy.cs" />
<Compile Include="Models\EndPoint.cs" />
<Compile Include="Extensions\TcpExtensions.cs" />
<Compile Include="Http\Request.cs" />
<Compile Include="Http\Response.cs" />
<Compile Include="Models\ExternalProxy.cs" />
<Compile Include="Network\Tcp\TcpConnection.cs" /> <Compile Include="Network\Tcp\TcpConnection.cs" />
<Compile Include="Network\Tcp\TcpConnectionFactory.cs" /> <Compile Include="Network\Tcp\TcpConnectionFactory.cs" />
<Compile Include="Models\HttpHeader.cs" />
<Compile Include="Http\HttpWebClient.cs" />
<Compile Include="Network\WinAuth\Security\Common.cs" /> <Compile Include="Network\WinAuth\Security\Common.cs" />
<Compile Include="Network\WinAuth\Security\WinAuthEndPoint.cs" />
<Compile Include="Network\WinAuth\Security\LittleEndian.cs" /> <Compile Include="Network\WinAuth\Security\LittleEndian.cs" />
<Compile Include="Network\WinAuth\Security\Message.cs" /> <Compile Include="Network\WinAuth\Security\Message.cs" />
<Compile Include="Network\WinAuth\Security\State.cs" /> <Compile Include="Network\WinAuth\Security\State.cs" />
<Compile Include="Network\WinAuth\Security\WinAuthEndPoint.cs" />
<Compile Include="Network\WinAuth\WinAuthHandler.cs" /> <Compile Include="Network\WinAuth\WinAuthHandler.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Shared\ProxyConstants.cs" />
<Compile Include="WinAuthHandler.cs" />
<Compile Include="CertificateHandler.cs" />
<Compile Include="ProxyAuthorizationHandler.cs" /> <Compile Include="ProxyAuthorizationHandler.cs" />
<Compile Include="ProxyServer.cs" />
<Compile Include="RequestHandler.cs" /> <Compile Include="RequestHandler.cs" />
<Compile Include="ResponseHandler.cs" /> <Compile Include="ResponseHandler.cs" />
<Compile Include="Helpers\CustomBinaryReader.cs" /> </ItemGroup>
<Compile Include="ProxyServer.cs" /> <ItemGroup>
<Compile Include="EventArguments\SessionEventArgs.cs" /> <Compile Include="Helpers\WinHttp\NativeMethods.WinHttp.cs" />
<Compile Include="Helpers\Tcp.cs" /> <Compile Include="Helpers\WinHttp\WinHttpHandle.cs" />
<Compile Include="Extensions\StreamExtensions.cs" /> <Compile Include="Helpers\WinHttp\WinHttpWebProxyFinder.cs" />
<Compile Include="Http\Responses\OkResponse.cs" /> <Compile Include="Helpers\NativeMethods.SystemProxy.cs" />
<Compile Include="Http\Responses\RedirectResponse.cs" /> <Compile Include="Helpers\NativeMethods.Tcp.cs" />
<Compile Include="Shared\ProxyConstants.cs" /> <Compile Include="Helpers\ProxyInfo.cs" />
<Compile Include="Helpers\RunTime.cs" />
<Compile Include="Helpers\SystemProxy.cs" />
<Compile Include="Network\Tcp\TcpRow.cs" /> <Compile Include="Network\Tcp\TcpRow.cs" />
<Compile Include="Network\Tcp\TcpTable.cs" /> <Compile Include="Network\Tcp\TcpTable.cs" />
<Compile Include="WinAuthHandler.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<COMReference Include="CERTENROLLLib" Condition="'$(OS)' != 'Unix'"> <COMReference Include="CERTENROLLLib" Condition="'$(OS)' != 'Unix'">
...@@ -148,6 +164,7 @@ ...@@ -148,6 +164,7 @@
<None Include="packages.config" /> <None Include="packages.config" />
<None Include="StrongNameKey.snk" /> <None Include="StrongNameKey.snk" />
</ItemGroup> </ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" /> <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
......
...@@ -5,16 +5,14 @@ using System.Threading.Tasks; ...@@ -5,16 +5,14 @@ using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.WinAuth; using Titanium.Web.Proxy.Network.WinAuth;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
public partial class ProxyServer public partial class ProxyServer
{ {
//possible header names //possible header names
private static List<string> authHeaderNames private static readonly List<string> authHeaderNames = new List<string>
= new List<string>() { {
"WWW-Authenticate", "WWW-Authenticate",
//IIS 6.0 messed up names below //IIS 6.0 messed up names below
"WWWAuthenticate", "WWWAuthenticate",
...@@ -23,8 +21,8 @@ namespace Titanium.Web.Proxy ...@@ -23,8 +21,8 @@ namespace Titanium.Web.Proxy
"KerberosAuthorization" "KerberosAuthorization"
}; };
private static List<string> authSchemes private static readonly List<string> authSchemes = new List<string>
= new List<string>() { {
"NTLM", "NTLM",
"Negotiate", "Negotiate",
"Kerberos" "Kerberos"
...@@ -45,10 +43,9 @@ namespace Titanium.Web.Proxy ...@@ -45,10 +43,9 @@ namespace Titanium.Web.Proxy
HttpHeader authHeader = null; HttpHeader authHeader = null;
//check in non-unique headers first //check in non-unique headers first
var header = args.WebSession.Response var header =
.NonUniqueResponseHeaders args.WebSession.Response.ResponseHeaders.NonUniqueHeaders.FirstOrDefault(
.FirstOrDefault(x => x => authHeaderNames.Any(y => x.Key.Equals(y, StringComparison.OrdinalIgnoreCase)));
authHeaderNames.Any(y => x.Key.Equals(y, StringComparison.OrdinalIgnoreCase)));
if (!header.Equals(new KeyValuePair<string, List<HttpHeader>>())) if (!header.Equals(new KeyValuePair<string, List<HttpHeader>>()))
{ {
...@@ -57,20 +54,16 @@ namespace Titanium.Web.Proxy ...@@ -57,20 +54,16 @@ namespace Titanium.Web.Proxy
if (headerName != null) if (headerName != null)
{ {
authHeader = args.WebSession.Response authHeader = args.WebSession.Response.ResponseHeaders.NonUniqueHeaders[headerName]
.NonUniqueResponseHeaders[headerName] .FirstOrDefault(x => authSchemes.Any(y => x.Value.StartsWith(y, StringComparison.OrdinalIgnoreCase)));
.Where(x => authSchemes.Any(y => x.Value.StartsWith(y, StringComparison.OrdinalIgnoreCase)))
.FirstOrDefault();
} }
//check in unique headers //check in unique headers
if (authHeader == null) if (authHeader == null)
{ {
//check in non-unique headers first //check in non-unique headers first
var uHeader = args.WebSession.Response var uHeader =
.ResponseHeaders args.WebSession.Response.ResponseHeaders.Headers.FirstOrDefault(x => authHeaderNames.Any(y => x.Key.Equals(y, StringComparison.OrdinalIgnoreCase)));
.FirstOrDefault(x =>
authHeaderNames.Any(y => x.Key.Equals(y, StringComparison.OrdinalIgnoreCase)));
if (!uHeader.Equals(new KeyValuePair<string, HttpHeader>())) if (!uHeader.Equals(new KeyValuePair<string, HttpHeader>()))
{ {
...@@ -79,66 +72,63 @@ namespace Titanium.Web.Proxy ...@@ -79,66 +72,63 @@ namespace Titanium.Web.Proxy
if (headerName != null) if (headerName != null)
{ {
authHeader = authSchemes.Any(x => args.WebSession.Response authHeader = authSchemes.Any(x => args.WebSession.Response.ResponseHeaders.Headers[headerName].Value
.ResponseHeaders[headerName].Value.StartsWith(x, StringComparison.OrdinalIgnoreCase)) ? .StartsWith(x, StringComparison.OrdinalIgnoreCase))
args.WebSession.Response.ResponseHeaders[headerName] : null; ? args.WebSession.Response.ResponseHeaders.Headers[headerName]
: null;
} }
} }
if (authHeader != null) if (authHeader != null)
{ {
var scheme = authSchemes.FirstOrDefault(x => authHeader.Value.Equals(x, StringComparison.OrdinalIgnoreCase)); string scheme = authSchemes.FirstOrDefault(x => authHeader.Value.Equals(x, StringComparison.OrdinalIgnoreCase));
//clear any existing headers to avoid confusing bad servers //clear any existing headers to avoid confusing bad servers
if (args.WebSession.Request.NonUniqueRequestHeaders.ContainsKey("Authorization")) if (args.WebSession.Request.RequestHeaders.NonUniqueHeaders.ContainsKey("Authorization"))
{ {
args.WebSession.Request.NonUniqueRequestHeaders.Remove("Authorization"); args.WebSession.Request.RequestHeaders.NonUniqueHeaders.Remove("Authorization");
} }
//initial value will match exactly any of the schemes //initial value will match exactly any of the schemes
if (scheme != null) if (scheme != null)
{ {
var clientToken = WinAuthHandler.GetInitialAuthToken(args.WebSession.Request.Host, scheme, args.Id); string clientToken = WinAuthHandler.GetInitialAuthToken(args.WebSession.Request.Host, scheme, args.Id);
var auth = new HttpHeader("Authorization", string.Concat(scheme, clientToken)); var auth = new HttpHeader("Authorization", string.Concat(scheme, clientToken));
//replace existing authorization header if any //replace existing authorization header if any
if (args.WebSession.Request.RequestHeaders.ContainsKey("Authorization")) if (args.WebSession.Request.RequestHeaders.Headers.ContainsKey("Authorization"))
{ {
args.WebSession.Request.RequestHeaders["Authorization"] = auth; args.WebSession.Request.RequestHeaders.Headers["Authorization"] = auth;
} }
else else
{ {
args.WebSession.Request.RequestHeaders.Add("Authorization", auth); args.WebSession.Request.RequestHeaders.Headers.Add("Authorization", auth);
} }
//don't need to send body for Authorization request //don't need to send body for Authorization request
if(args.WebSession.Request.HasBody) if (args.WebSession.Request.HasBody)
{ {
args.WebSession.Request.ContentLength = 0; args.WebSession.Request.ContentLength = 0;
} }
} }
//challenge value will start with any of the scheme selected //challenge value will start with any of the scheme selected
else else
{ {
scheme = authSchemes.FirstOrDefault(x => authHeader.Value.StartsWith(x, StringComparison.OrdinalIgnoreCase) scheme = authSchemes.FirstOrDefault(x => authHeader.Value.StartsWith(x, StringComparison.OrdinalIgnoreCase) &&
&& authHeader.Value.Length > x.Length + 1); authHeader.Value.Length > x.Length + 1);
var serverToken = authHeader.Value.Substring(scheme.Length + 1); string serverToken = authHeader.Value.Substring(scheme.Length + 1);
var clientToken = WinAuthHandler.GetFinalAuthToken(args.WebSession.Request.Host, serverToken, args.Id); string clientToken = WinAuthHandler.GetFinalAuthToken(args.WebSession.Request.Host, serverToken, args.Id);
//there will be an existing header from initial client request //there will be an existing header from initial client request
args.WebSession.Request.RequestHeaders["Authorization"] args.WebSession.Request.RequestHeaders.Headers["Authorization"] = new HttpHeader("Authorization", string.Concat(scheme, clientToken));
= new HttpHeader("Authorization", string.Concat(scheme, clientToken));
//send body for final auth request //send body for final auth request
if (args.WebSession.Request.HasBody) if (args.WebSession.Request.HasBody)
{ {
args.WebSession.Request.ContentLength args.WebSession.Request.ContentLength = args.WebSession.Request.RequestBody.Length;
= args.WebSession.Request.RequestBody.Length;
} }
} }
//Need to revisit this. //Need to revisit this.
...@@ -150,13 +140,11 @@ namespace Titanium.Web.Proxy ...@@ -150,13 +140,11 @@ namespace Titanium.Web.Proxy
//request again with updated authorization header //request again with updated authorization header
//and server cookies //and server cookies
var disposed = await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args, false); bool disposed = await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args, false);
return disposed; return disposed;
} }
return false; return false;
} }
} }
} }
...@@ -33,7 +33,7 @@ assembly_info: ...@@ -33,7 +33,7 @@ assembly_info:
assembly_informational_version: "{version}" assembly_informational_version: "{version}"
# to disable automatic tests # to disable automatic tests
test: off test: on
# skip building commits that add tags (such as release tag) # skip building commits that add tags (such as release tag)
skip_tags: true skip_tags: 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