Unverified Commit b69506ef authored by honfika's avatar honfika Committed by GitHub

Merge pull request #416 from justcoding121/develop

merge to beta
parents d3829eab 498e9644
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
......
...@@ -9,19 +9,19 @@ namespace Titanium.Web.Proxy.Examples.Basic.Helpers ...@@ -9,19 +9,19 @@ namespace Titanium.Web.Proxy.Examples.Basic.Helpers
/// </summary> /// </summary>
internal static class ConsoleHelper internal static class ConsoleHelper
{ {
const uint ENABLE_QUICK_EDIT = 0x0040; private const uint ENABLE_QUICK_EDIT = 0x0040;
// STD_INPUT_HANDLE (DWORD): -10 is the standard input device. // STD_INPUT_HANDLE (DWORD): -10 is the standard input device.
const int STD_INPUT_HANDLE = -10; private const int STD_INPUT_HANDLE = -10;
[DllImport("kernel32.dll", SetLastError = true)] [DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr GetStdHandle(int nStdHandle); private static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll")] [DllImport("kernel32.dll")]
static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode); private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
[DllImport("kernel32.dll")] [DllImport("kernel32.dll")]
static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode); private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
internal static bool DisableQuickEditMode() internal static bool DisableQuickEditMode()
{ {
......
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following
......
...@@ -17,13 +17,16 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -17,13 +17,16 @@ namespace Titanium.Web.Proxy.Examples.Basic
private readonly object lockObj = new object(); private readonly object lockObj = new object();
private readonly ProxyServer proxyServer; private readonly ProxyServer proxyServer;
private ExplicitProxyEndPoint explicitEndPoint;
//keep track of request headers //keep track of request headers
private readonly IDictionary<Guid, HeaderCollection> requestHeaderHistory = new ConcurrentDictionary<Guid, HeaderCollection>(); private readonly IDictionary<Guid, HeaderCollection> requestHeaderHistory =
new ConcurrentDictionary<Guid, HeaderCollection>();
//keep track of response headers //keep track of response headers
private readonly IDictionary<Guid, HeaderCollection> responseHeaderHistory = new ConcurrentDictionary<Guid, HeaderCollection>(); private readonly IDictionary<Guid, HeaderCollection> responseHeaderHistory =
new ConcurrentDictionary<Guid, HeaderCollection>();
private ExplicitProxyEndPoint explicitEndPoint;
//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
...@@ -78,13 +81,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -78,13 +81,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
//proxyServer.EnableWinAuth = true; //proxyServer.EnableWinAuth = true;
explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000) explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000);
{
//Use self-issued generic certificate on all https requests
//Optimizes performance by not creating a certificate for each https-enabled domain
//Useful when certificate trust is not required by proxy clients
//GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password")
};
//Fired when a CONNECT request is received //Fired when a CONNECT request is received
explicitEndPoint.BeforeTunnelConnectRequest += OnBeforeTunnelConnectRequest; explicitEndPoint.BeforeTunnelConnectRequest += OnBeforeTunnelConnectRequest;
...@@ -111,7 +108,10 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -111,7 +108,10 @@ namespace Titanium.Web.Proxy.Examples.Basic
//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} ", endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port); {
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ", endPoint.GetType().Name,
endPoint.IpAddress, endPoint.Port);
}
#if NETSTANDARD2_0 #if NETSTANDARD2_0
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup> </startup>
</configuration> </configuration>
\ No newline at end of file
...@@ -18,7 +18,8 @@ ...@@ -18,7 +18,8 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<GridSplitter Grid.Column="1" Grid.Row="0" HorizontalAlignment="Stretch" /> <GridSplitter Grid.Column="1" Grid.Row="0" HorizontalAlignment="Stretch" />
<ListView Grid.Column="0" Grid.Row="0" HorizontalAlignment="Stretch" ItemsSource="{Binding Sessions}" SelectedItem="{Binding SelectedSession}" <ListView Grid.Column="0" Grid.Row="0" HorizontalAlignment="Stretch" ItemsSource="{Binding Sessions}"
SelectedItem="{Binding SelectedSession}"
KeyDown="ListViewSessions_OnKeyDown"> KeyDown="ListViewSessions_OnKeyDown">
<ListView.View> <ListView.View>
<GridView> <GridView>
......
...@@ -20,44 +20,18 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -20,44 +20,18 @@ namespace Titanium.Web.Proxy.Examples.Wpf
/// </summary> /// </summary>
public partial class MainWindow : Window public partial class MainWindow : Window
{ {
private readonly ProxyServer proxyServer;
private int lastSessionNumber;
public ObservableCollection<SessionListItem> Sessions { get; } = new ObservableCollection<SessionListItem>();
public SessionListItem SelectedSession
{
get => selectedSession;
set
{
if (value != selectedSession)
{
selectedSession = value;
SelectedSessionChanged();
}
}
}
public static readonly DependencyProperty ClientConnectionCountProperty = DependencyProperty.Register( public static readonly DependencyProperty ClientConnectionCountProperty = DependencyProperty.Register(
nameof(ClientConnectionCount), typeof(int), typeof(MainWindow), new PropertyMetadata(default(int))); nameof(ClientConnectionCount), typeof(int), typeof(MainWindow), new PropertyMetadata(default(int)));
public int ClientConnectionCount
{
get => (int)GetValue(ClientConnectionCountProperty);
set => SetValue(ClientConnectionCountProperty, value);
}
public static readonly DependencyProperty ServerConnectionCountProperty = DependencyProperty.Register( public static readonly DependencyProperty ServerConnectionCountProperty = DependencyProperty.Register(
nameof(ServerConnectionCount), typeof(int), typeof(MainWindow), new PropertyMetadata(default(int))); nameof(ServerConnectionCount), typeof(int), typeof(MainWindow), new PropertyMetadata(default(int)));
public int ServerConnectionCount private readonly ProxyServer proxyServer;
{
get => (int)GetValue(ServerConnectionCountProperty);
set => SetValue(ServerConnectionCountProperty, value);
}
private readonly Dictionary<HttpWebClient, SessionListItem> sessionDictionary = new Dictionary<HttpWebClient, SessionListItem>(); private readonly Dictionary<HttpWebClient, SessionListItem> sessionDictionary =
new Dictionary<HttpWebClient, SessionListItem>();
private int lastSessionNumber;
private SessionListItem selectedSession; private SessionListItem selectedSession;
public MainWindow() public MainWindow()
...@@ -105,8 +79,14 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -105,8 +79,14 @@ namespace Titanium.Web.Proxy.Examples.Wpf
proxyServer.AfterResponse += ProxyServer_AfterResponse; proxyServer.AfterResponse += ProxyServer_AfterResponse;
explicitEndPoint.BeforeTunnelConnectRequest += ProxyServer_BeforeTunnelConnectRequest; explicitEndPoint.BeforeTunnelConnectRequest += ProxyServer_BeforeTunnelConnectRequest;
explicitEndPoint.BeforeTunnelConnectResponse += ProxyServer_BeforeTunnelConnectResponse; explicitEndPoint.BeforeTunnelConnectResponse += ProxyServer_BeforeTunnelConnectResponse;
proxyServer.ClientConnectionCountChanged += delegate { Dispatcher.Invoke(() => { ClientConnectionCount = proxyServer.ClientConnectionCount; }); }; proxyServer.ClientConnectionCountChanged += delegate
proxyServer.ServerConnectionCountChanged += delegate { Dispatcher.Invoke(() => { ServerConnectionCount = proxyServer.ServerConnectionCount; }); }; {
Dispatcher.Invoke(() => { ClientConnectionCount = proxyServer.ClientConnectionCount; });
};
proxyServer.ServerConnectionCountChanged += delegate
{
Dispatcher.Invoke(() => { ServerConnectionCount = proxyServer.ServerConnectionCount; });
};
proxyServer.Start(); proxyServer.Start();
proxyServer.SetAsSystemProxy(explicitEndPoint, ProxyProtocolType.AllHttp); proxyServer.SetAsSystemProxy(explicitEndPoint, ProxyProtocolType.AllHttp);
...@@ -114,6 +94,33 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -114,6 +94,33 @@ namespace Titanium.Web.Proxy.Examples.Wpf
InitializeComponent(); InitializeComponent();
} }
public ObservableCollection<SessionListItem> Sessions { get; } = new ObservableCollection<SessionListItem>();
public SessionListItem SelectedSession
{
get => selectedSession;
set
{
if (value != selectedSession)
{
selectedSession = value;
SelectedSessionChanged();
}
}
}
public int ClientConnectionCount
{
get => (int)GetValue(ClientConnectionCountProperty);
set => SetValue(ClientConnectionCountProperty, value);
}
public int ServerConnectionCount
{
get => (int)GetValue(ServerConnectionCountProperty);
set => SetValue(ServerConnectionCountProperty, value);
}
private async Task ProxyServer_BeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e) private async Task ProxyServer_BeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
{ {
string hostname = e.WebSession.Request.RequestUri.Host; string hostname = e.WebSession.Request.RequestUri.Host;
...@@ -122,10 +129,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -122,10 +129,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
e.DecryptSsl = false; e.DecryptSsl = false;
} }
await Dispatcher.InvokeAsync(() => await Dispatcher.InvokeAsync(() => { AddSession(e); });
{
AddSession(e);
});
} }
private async Task ProxyServer_BeforeTunnelConnectResponse(object sender, TunnelConnectSessionEventArgs e) private async Task ProxyServer_BeforeTunnelConnectResponse(object sender, TunnelConnectSessionEventArgs e)
...@@ -142,10 +146,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -142,10 +146,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
private async Task ProxyServer_BeforeRequest(object sender, SessionEventArgs e) private async Task ProxyServer_BeforeRequest(object sender, SessionEventArgs e)
{ {
SessionListItem item = null; SessionListItem item = null;
await Dispatcher.InvokeAsync(() => await Dispatcher.InvokeAsync(() => { item = AddSession(e); });
{
item = AddSession(e);
});
if (e.WebSession.Request.HasBody) if (e.WebSession.Request.HasBody)
{ {
...@@ -172,10 +173,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -172,10 +173,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
e.WebSession.Response.KeepBody = true; e.WebSession.Response.KeepBody = true;
await e.GetResponseBody(); await e.GetResponseBody();
await Dispatcher.InvokeAsync(() => await Dispatcher.InvokeAsync(() => { item.Update(); });
{
item.Update();
});
} }
} }
} }
...@@ -207,7 +205,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -207,7 +205,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
{ {
Number = lastSessionNumber, Number = lastSessionNumber,
WebSession = e.WebSession, WebSession = e.WebSession,
IsTunnelConnect = isTunnelConnect, IsTunnelConnect = isTunnelConnect
}; };
if (isTunnelConnect || e.WebSession.Request.UpgradeToWebSocket) if (isTunnelConnect || e.WebSession.Request.UpgradeToWebSocket)
......
...@@ -36,33 +36,41 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -36,33 +36,41 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// Indicates that the value of the marked element could be <c>null</c> sometimes, /// 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. /// so the check for <c>null</c> is necessary before its usage.
/// </summary> /// </summary>
/// <example><code> /// <example>
/// <code>
/// [CanBeNull] object Test() => null; /// [CanBeNull] object Test() => null;
/// ///
/// void UseTest() { /// void UseTest() {
/// var p = Test(); /// var p = Test();
/// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException'
/// } /// }
/// </code></example> /// </code>
/// </example>
[AttributeUsage( [AttributeUsage(
AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |
AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event |
AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)]
public sealed class CanBeNullAttribute : Attribute { } public sealed class CanBeNullAttribute : Attribute
{
}
/// <summary> /// <summary>
/// Indicates that the value of the marked element could never be <c>null</c>. /// Indicates that the value of the marked element could never be <c>null</c>.
/// </summary> /// </summary>
/// <example><code> /// <example>
/// <code>
/// [NotNull] object Foo() { /// [NotNull] object Foo() {
/// return null; // Warning: Possible 'null' assignment /// return null; // Warning: Possible 'null' assignment
/// } /// }
/// </code></example> /// </code>
/// </example>
[AttributeUsage( [AttributeUsage(
AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |
AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event |
AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)]
public sealed class NotNullAttribute : Attribute { } public sealed class NotNullAttribute : Attribute
{
}
/// <summary> /// <summary>
/// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task
...@@ -72,7 +80,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -72,7 +80,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
[AttributeUsage( [AttributeUsage(
AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |
AttributeTargets.Delegate | AttributeTargets.Field)] AttributeTargets.Delegate | AttributeTargets.Field)]
public sealed class ItemNotNullAttribute : Attribute { } public sealed class ItemNotNullAttribute : Attribute
{
}
/// <summary> /// <summary>
/// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task
...@@ -82,21 +92,25 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -82,21 +92,25 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
[AttributeUsage( [AttributeUsage(
AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |
AttributeTargets.Delegate | AttributeTargets.Field)] AttributeTargets.Delegate | AttributeTargets.Field)]
public sealed class ItemCanBeNullAttribute : Attribute { } public sealed class ItemCanBeNullAttribute : Attribute
{
}
/// <summary> /// <summary>
/// Indicates that the marked method builds string by format pattern and (optional) arguments. /// 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 /// 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. /// should be in <see cref="string.Format(IFormatProvider,string,object[])" />-like form.
/// </summary> /// </summary>
/// <example><code> /// <example>
/// <code>
/// [StringFormatMethod("message")] /// [StringFormatMethod("message")]
/// void ShowError(string message, params object[] args) { /* do something */ } /// void ShowError(string message, params object[] args) { /* do something */ }
/// ///
/// void Foo() { /// void Foo() {
/// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string
/// } /// }
/// </code></example> /// </code>
/// </example>
[AttributeUsage( [AttributeUsage(
AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Method |
AttributeTargets.Property | AttributeTargets.Delegate)] AttributeTargets.Property | AttributeTargets.Delegate)]
...@@ -110,7 +124,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -110,7 +124,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
FormatParameterName = formatParameterName; FormatParameterName = formatParameterName;
} }
[NotNull] public string FormatParameterName { get; private set; } [NotNull]
public string FormatParameterName { get; }
} }
/// <summary> /// <summary>
...@@ -127,22 +142,27 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -127,22 +142,27 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
Name = name; Name = name;
} }
[NotNull] public string Name { get; private set; } [NotNull]
public string Name { get; }
} }
/// <summary> /// <summary>
/// Indicates that the function argument should be string literal and match one /// Indicates that the function argument should be string literal and match one
/// of the parameters of the caller function. For example, ReSharper annotates /// of the parameters of the caller function. For example, ReSharper annotates
/// the parameter of <see cref="System.ArgumentNullException"/>. /// the parameter of <see cref="System.ArgumentNullException" />.
/// </summary> /// </summary>
/// <example><code> /// <example>
/// <code>
/// void Foo(string param) { /// void Foo(string param) {
/// if (param == null) /// if (param == null)
/// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol
/// } /// }
/// </code></example> /// </code>
/// </example>
[AttributeUsage(AttributeTargets.Parameter)] [AttributeUsage(AttributeTargets.Parameter)]
public sealed class InvokerParameterNameAttribute : Attribute { } public sealed class InvokerParameterNameAttribute : Attribute
{
}
/// <summary> /// <summary>
/// Indicates that the method is contained in a type that implements /// Indicates that the method is contained in a type that implements
...@@ -152,14 +172,25 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -152,14 +172,25 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// <remarks> /// <remarks>
/// The method should be non-static and conform to one of the supported signatures: /// The method should be non-static and conform to one of the supported signatures:
/// <list> /// <list>
/// <item><c>NotifyChanged(string)</c></item> /// <item>
/// <item><c>NotifyChanged(params string[])</c></item> /// <c>NotifyChanged(string)</c>
/// <item><c>NotifyChanged{T}(Expression{Func{T}})</c></item> /// </item>
/// <item><c>NotifyChanged{T,U}(Expression{Func{T,U}})</c></item> /// <item>
/// <item><c>SetProperty{T}(ref T, T, string)</c></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> /// </list>
/// </remarks> /// </remarks>
/// <example><code> /// <example>
/// <code>
/// public class Foo : INotifyPropertyChanged { /// public class Foo : INotifyPropertyChanged {
/// public event PropertyChangedEventHandler PropertyChanged; /// public event PropertyChangedEventHandler PropertyChanged;
/// ///
...@@ -176,22 +207,34 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -176,22 +207,34 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// </code> /// </code>
/// Examples of generated notifications: /// Examples of generated notifications:
/// <list> /// <list>
/// <item><c>NotifyChanged("Property")</c></item> /// <item>
/// <item><c>NotifyChanged(() =&gt; Property)</c></item> /// <c>NotifyChanged("Property")</c>
/// <item><c>NotifyChanged((VM x) =&gt; x.Property)</c></item> /// </item>
/// <item><c>SetProperty(ref myField, value, "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> /// </list>
/// </example> /// </example>
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute
{ {
public NotifyPropertyChangedInvocatorAttribute() { } public NotifyPropertyChangedInvocatorAttribute()
{
}
public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName) public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName)
{ {
ParameterName = parameterName; ParameterName = parameterName;
} }
[CanBeNull] public string ParameterName { get; private set; } [CanBeNull]
public string ParameterName { get; }
} }
/// <summary> /// <summary>
...@@ -206,43 +249,57 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -206,43 +249,57 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// <item>Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value}</item> /// <item>Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value}</item>
/// <item>Value ::= true | false | null | notnull | canbenull</item> /// <item>Value ::= true | false | null | notnull | canbenull</item>
/// </list> /// </list>
/// If method has single input parameter, it's name could be omitted.<br/> /// 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 /// 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/> /// 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/> /// 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 /// 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 /// 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/> /// for applicability and applied per each program state tracked by R# analysis.<br />
/// </syntax> /// </syntax>
/// <examples><list> /// <examples>
/// <item><code> /// <list>
/// <item>
/// <code>
/// [ContractAnnotation("=&gt; halt")] /// [ContractAnnotation("=&gt; halt")]
/// public void TerminationMethod() /// public void TerminationMethod()
/// </code></item> /// </code>
/// <item><code> /// </item>
/// <item>
/// <code>
/// [ContractAnnotation("halt &lt;= condition: false")] /// [ContractAnnotation("halt &lt;= condition: false")]
/// public void Assert(bool condition, string text) // regular assertion method /// public void Assert(bool condition, string text) // regular assertion method
/// </code></item> /// </code>
/// <item><code> /// </item>
/// <item>
/// <code>
/// [ContractAnnotation("s:null =&gt; true")] /// [ContractAnnotation("s:null =&gt; true")]
/// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty()
/// </code></item> /// </code>
/// <item><code> /// </item>
/// <item>
/// <code>
/// // A method that returns null if the parameter is null, /// // A method that returns null if the parameter is null,
/// // and not null if the parameter is not null /// // and not null if the parameter is not null
/// [ContractAnnotation("null =&gt; null; notnull =&gt; notnull")] /// [ContractAnnotation("null =&gt; null; notnull =&gt; notnull")]
/// public object Transform(object data) /// public object Transform(object data)
/// </code></item> /// </code>
/// <item><code> /// </item>
/// <item>
/// <code>
/// [ContractAnnotation("=&gt; true, result: notnull; =&gt; false, result: null")] /// [ContractAnnotation("=&gt; true, result: notnull; =&gt; false, result: null")]
/// public bool TryParse(string s, out Person result) /// public bool TryParse(string s, out Person result)
/// </code></item> /// </code>
/// </list></examples> /// </item>
/// </list>
/// </examples>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public sealed class ContractAnnotationAttribute : Attribute public sealed class ContractAnnotationAttribute : Attribute
{ {
public ContractAnnotationAttribute([NotNull] string contract) public ContractAnnotationAttribute([NotNull] string contract)
: this(contract, false) { } : this(contract, false)
{
}
public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates)
{ {
...@@ -250,31 +307,36 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -250,31 +307,36 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
ForceFullStates = forceFullStates; ForceFullStates = forceFullStates;
} }
[NotNull] public string Contract { get; private set; } [NotNull]
public string Contract { get; }
public bool ForceFullStates { get; private set; } public bool ForceFullStates { get; }
} }
/// <summary> /// <summary>
/// Indicates that marked element should be localized or not. /// Indicates that marked element should be localized or not.
/// </summary> /// </summary>
/// <example><code> /// <example>
/// <code>
/// [LocalizationRequiredAttribute(true)] /// [LocalizationRequiredAttribute(true)]
/// class Foo { /// class Foo {
/// string str = "my string"; // Warning: Localizable string /// string str = "my string"; // Warning: Localizable string
/// } /// }
/// </code></example> /// </code>
/// </example>
[AttributeUsage(AttributeTargets.All)] [AttributeUsage(AttributeTargets.All)]
public sealed class LocalizationRequiredAttribute : Attribute public sealed class LocalizationRequiredAttribute : Attribute
{ {
public LocalizationRequiredAttribute() : this(true) { } public LocalizationRequiredAttribute() : this(true)
{
}
public LocalizationRequiredAttribute(bool required) public LocalizationRequiredAttribute(bool required)
{ {
Required = required; Required = required;
} }
public bool Required { get; private set; } public bool Required { get; }
} }
/// <summary> /// <summary>
...@@ -283,7 +345,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -283,7 +345,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// should be used instead. However, using '==' or '!=' for comparison /// should be used instead. However, using '==' or '!=' for comparison
/// with <c>null</c> is always permitted. /// with <c>null</c> is always permitted.
/// </summary> /// </summary>
/// <example><code> /// <example>
/// <code>
/// [CannotApplyEqualityOperator] /// [CannotApplyEqualityOperator]
/// class NoEquality { } /// class NoEquality { }
/// ///
...@@ -296,21 +359,26 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -296,21 +359,26 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// } /// }
/// } /// }
/// } /// }
/// </code></example> /// </code>
/// </example>
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)] [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)]
public sealed class CannotApplyEqualityOperatorAttribute : Attribute { } public sealed class CannotApplyEqualityOperatorAttribute : Attribute
{
}
/// <summary> /// <summary>
/// When applied to a target attribute, specifies a requirement for any type marked /// 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. /// with the target attribute to implement or inherit specific type or types.
/// </summary> /// </summary>
/// <example><code> /// <example>
/// <code>
/// [BaseTypeRequired(typeof(IComponent)] // Specify requirement /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement
/// class ComponentAttribute : Attribute { } /// class ComponentAttribute : Attribute { }
/// ///
/// [Component] // ComponentAttribute requires implementing IComponent interface /// [Component] // ComponentAttribute requires implementing IComponent interface
/// class MyComponent : IComponent { } /// class MyComponent : IComponent { }
/// </code></example> /// </code>
/// </example>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
[BaseTypeRequired(typeof(Attribute))] [BaseTypeRequired(typeof(Attribute))]
public sealed class BaseTypeRequiredAttribute : Attribute public sealed class BaseTypeRequiredAttribute : Attribute
...@@ -320,7 +388,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -320,7 +388,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
BaseType = baseType; BaseType = baseType;
} }
[NotNull] public Type BaseType { get; private set; } [NotNull]
public Type BaseType { get; }
} }
/// <summary> /// <summary>
...@@ -331,13 +400,19 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -331,13 +400,19 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
public sealed class UsedImplicitlyAttribute : Attribute public sealed class UsedImplicitlyAttribute : Attribute
{ {
public UsedImplicitlyAttribute() public UsedImplicitlyAttribute()
: this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default)
{
}
public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags)
: this(useKindFlags, ImplicitUseTargetFlags.Default) { } : this(useKindFlags, ImplicitUseTargetFlags.Default)
{
}
public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags)
: this(ImplicitUseKindFlags.Default, targetFlags) { } : this(ImplicitUseKindFlags.Default, targetFlags)
{
}
public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags)
{ {
...@@ -345,9 +420,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -345,9 +420,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
TargetFlags = targetFlags; TargetFlags = targetFlags;
} }
public ImplicitUseKindFlags UseKindFlags { get; private set; } public ImplicitUseKindFlags UseKindFlags { get; }
public ImplicitUseTargetFlags TargetFlags { get; private set; } public ImplicitUseTargetFlags TargetFlags { get; }
} }
/// <summary> /// <summary>
...@@ -358,13 +433,19 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -358,13 +433,19 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
public sealed class MeansImplicitUseAttribute : Attribute public sealed class MeansImplicitUseAttribute : Attribute
{ {
public MeansImplicitUseAttribute() public MeansImplicitUseAttribute()
: this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default)
{
}
public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags)
: this(useKindFlags, ImplicitUseTargetFlags.Default) { } : this(useKindFlags, ImplicitUseTargetFlags.Default)
{
}
public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags)
: this(ImplicitUseKindFlags.Default, targetFlags) { } : this(ImplicitUseKindFlags.Default, targetFlags)
{
}
public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags)
{ {
...@@ -372,39 +453,47 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -372,39 +453,47 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
TargetFlags = targetFlags; TargetFlags = targetFlags;
} }
[UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; private set; } [UsedImplicitly]
public ImplicitUseKindFlags UseKindFlags { get; private set; }
[UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; private set; } [UsedImplicitly]
public ImplicitUseTargetFlags TargetFlags { get; private set; }
} }
[Flags] [Flags]
public enum ImplicitUseKindFlags public enum ImplicitUseKindFlags
{ {
Default = Access | Assign | InstantiatedWithFixedConstructorSignature, Default = Access | Assign | InstantiatedWithFixedConstructorSignature,
/// <summary>Only entity marked with attribute considered used.</summary> /// <summary>Only entity marked with attribute considered used.</summary>
Access = 1, Access = 1,
/// <summary>Indicates implicit assignment to a member.</summary> /// <summary>Indicates implicit assignment to a member.</summary>
Assign = 2, Assign = 2,
/// <summary> /// <summary>
/// Indicates implicit instantiation of a type with fixed constructor signature. /// Indicates implicit instantiation of a type with fixed constructor signature.
/// That means any unused constructor parameters won't be reported as such. /// That means any unused constructor parameters won't be reported as such.
/// </summary> /// </summary>
InstantiatedWithFixedConstructorSignature = 4, InstantiatedWithFixedConstructorSignature = 4,
/// <summary>Indicates implicit instantiation of a type.</summary> /// <summary>Indicates implicit instantiation of a type.</summary>
InstantiatedNoFixedConstructorSignature = 8, InstantiatedNoFixedConstructorSignature = 8
} }
/// <summary> /// <summary>
/// Specify what is considered used implicitly when marked /// Specify what is considered used implicitly when marked
/// with <see cref="MeansImplicitUseAttribute"/> or <see cref="UsedImplicitlyAttribute"/>. /// with <see cref="MeansImplicitUseAttribute" /> or <see cref="UsedImplicitlyAttribute" />.
/// </summary> /// </summary>
[Flags] [Flags]
public enum ImplicitUseTargetFlags public enum ImplicitUseTargetFlags
{ {
Default = Itself, Default = Itself,
Itself = 1, Itself = 1,
/// <summary>Members of entity marked with attribute are considered used.</summary> /// <summary>Members of entity marked with attribute are considered used.</summary>
Members = 2, Members = 2,
/// <summary>Entity marked with attribute and all its members considered used.</summary> /// <summary>Entity marked with attribute and all its members considered used.</summary>
WithMembers = Itself | Members WithMembers = Itself | Members
} }
...@@ -416,14 +505,17 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -416,14 +505,17 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
[MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)]
public sealed class PublicAPIAttribute : Attribute public sealed class PublicAPIAttribute : Attribute
{ {
public PublicAPIAttribute() { } public PublicAPIAttribute()
{
}
public PublicAPIAttribute([NotNull] string comment) public PublicAPIAttribute([NotNull] string comment)
{ {
Comment = comment; Comment = comment;
} }
[CanBeNull] public string Comment { get; private set; } [CanBeNull]
public string Comment { get; }
} }
/// <summary> /// <summary>
...@@ -432,21 +524,27 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -432,21 +524,27 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// If the parameter is an enumerable, indicates that it is enumerated while the method is executed. /// If the parameter is an enumerable, indicates that it is enumerated while the method is executed.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Parameter)] [AttributeUsage(AttributeTargets.Parameter)]
public sealed class InstantHandleAttribute : Attribute { } public sealed class InstantHandleAttribute : Attribute
{
}
/// <summary> /// <summary>
/// Indicates that a method does not make any observable state changes. /// Indicates that a method does not make any observable state changes.
/// The same as <c>System.Diagnostics.Contracts.PureAttribute</c>. /// The same as <c>System.Diagnostics.Contracts.PureAttribute</c>.
/// </summary> /// </summary>
/// <example><code> /// <example>
/// <code>
/// [Pure] int Multiply(int x, int y) => x * y; /// [Pure] int Multiply(int x, int y) => x * y;
/// ///
/// void M() { /// void M() {
/// Multiply(123, 42); // Waring: Return value of pure method is not used /// Multiply(123, 42); // Waring: Return value of pure method is not used
/// } /// }
/// </code></example> /// </code>
/// </example>
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public sealed class PureAttribute : Attribute { } public sealed class PureAttribute : Attribute
{
}
/// <summary> /// <summary>
/// Indicates that the return value of method invocation must be used. /// Indicates that the return value of method invocation must be used.
...@@ -454,14 +552,17 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -454,14 +552,17 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public sealed class MustUseReturnValueAttribute : Attribute public sealed class MustUseReturnValueAttribute : Attribute
{ {
public MustUseReturnValueAttribute() { } public MustUseReturnValueAttribute()
{
}
public MustUseReturnValueAttribute([NotNull] string justification) public MustUseReturnValueAttribute([NotNull] string justification)
{ {
Justification = justification; Justification = justification;
} }
[CanBeNull] public string Justification { get; private set; } [CanBeNull]
public string Justification { get; }
} }
/// <summary> /// <summary>
...@@ -469,7 +570,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -469,7 +570,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// to get the value that type. This annotation is useful when you have some "context" value evaluated /// 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. /// and stored somewhere, meaning that all other ways to get this value must be consolidated with existing one.
/// </summary> /// </summary>
/// <example><code> /// <example>
/// <code>
/// class Foo { /// class Foo {
/// [ProvidesContext] IBarService _barService = ...; /// [ProvidesContext] IBarService _barService = ...;
/// ///
...@@ -478,11 +580,15 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -478,11 +580,15 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// // ^ Warning: use value of '_barService' field /// // ^ Warning: use value of '_barService' field
/// } /// }
/// } /// }
/// </code></example> /// </code>
/// </example>
[AttributeUsage( [AttributeUsage(
AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Method |
AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.GenericParameter)] AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct |
public sealed class ProvidesContextAttribute : Attribute { } AttributeTargets.GenericParameter)]
public sealed class ProvidesContextAttribute : Attribute
{
}
/// <summary> /// <summary>
/// Indicates that a parameter is a path to a file or a folder within a web project. /// Indicates that a parameter is a path to a file or a folder within a web project.
...@@ -491,14 +597,17 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -491,14 +597,17 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
[AttributeUsage(AttributeTargets.Parameter)] [AttributeUsage(AttributeTargets.Parameter)]
public sealed class PathReferenceAttribute : Attribute public sealed class PathReferenceAttribute : Attribute
{ {
public PathReferenceAttribute() { } public PathReferenceAttribute()
{
}
public PathReferenceAttribute([NotNull, PathReference] string basePath) public PathReferenceAttribute([NotNull] [PathReference] string basePath)
{ {
BasePath = basePath; BasePath = basePath;
} }
[CanBeNull] public string BasePath { get; private set; } [CanBeNull]
public string BasePath { get; }
} }
/// <summary> /// <summary>
...@@ -510,7 +619,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -510,7 +619,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// Template method body can contain valid source code and/or special comments starting with '$'. /// 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 /// 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. /// 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. /// Use the <see cref="MacroAttribute" /> attribute to specify macros for parameters.
/// </remarks> /// </remarks>
/// <example> /// <example>
/// In this example, the 'forEach' method is a source template available over all values /// In this example, the 'forEach' method is a source template available over all values
...@@ -525,16 +634,18 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -525,16 +634,18 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// </code> /// </code>
/// </example> /// </example>
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public sealed class SourceTemplateAttribute : Attribute { } public sealed class SourceTemplateAttribute : Attribute
{
}
/// <summary> /// <summary>
/// Allows specifying a macro for a parameter of a <see cref="SourceTemplateAttribute">source template</see>. /// Allows specifying a macro for a parameter of a <see cref="SourceTemplateAttribute">source template</see>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// You can apply the attribute on the whole method or on any of its additional parameters. The macro expression /// 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 /// 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 /// 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. /// for the parameter, set the <see cref="MacroAttribute.Editable" /> property value = -1.
/// </remarks> /// </remarks>
/// <example> /// <example>
/// Applying the attribute on a source template method: /// Applying the attribute on a source template method:
...@@ -562,7 +673,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -562,7 +673,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// Allows specifying a macro that will be executed for a <see cref="SourceTemplateAttribute">source template</see> /// Allows specifying a macro that will be executed for a <see cref="SourceTemplateAttribute">source template</see>
/// parameter when the template is expanded. /// parameter when the template is expanded.
/// </summary> /// </summary>
[CanBeNull] public string Expression { get; set; } [CanBeNull]
public string Expression { get; set; }
/// <summary> /// <summary>
/// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed. /// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed.
...@@ -571,17 +683,20 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -571,17 +683,20 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// If the target parameter is used several times in the template, only one occurrence becomes editable; /// 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, /// 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. /// use values >= 0. To make the parameter non-editable when the template is expanded, use -1.
/// </remarks>> /// </remarks>
/// >
public int Editable { get; set; } public int Editable { get; set; }
/// <summary> /// <summary>
/// Identifies the target parameter of a <see cref="SourceTemplateAttribute">source template</see> if the /// Identifies the target parameter of a <see cref="SourceTemplateAttribute">source template</see> if the
/// <see cref="MacroAttribute"/> is applied on a template method. /// <see cref="MacroAttribute" /> is applied on a template method.
/// </summary> /// </summary>
[CanBeNull] public string Target { get; set; } [CanBeNull]
public string Target { get; set; }
} }
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple =
true)]
public sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute public sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute
{ {
public AspMvcAreaMasterLocationFormatAttribute([NotNull] string format) public AspMvcAreaMasterLocationFormatAttribute([NotNull] string format)
...@@ -589,10 +704,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -589,10 +704,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
Format = format; Format = format;
} }
[NotNull] public string Format { get; private set; } [NotNull]
public string Format { get; }
} }
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple =
true)]
public sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute public sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute
{ {
public AspMvcAreaPartialViewLocationFormatAttribute([NotNull] string format) public AspMvcAreaPartialViewLocationFormatAttribute([NotNull] string format)
...@@ -600,10 +717,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -600,10 +717,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
Format = format; Format = format;
} }
[NotNull] public string Format { get; private set; } [NotNull]
public string Format { get; }
} }
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple =
true)]
public sealed class AspMvcAreaViewLocationFormatAttribute : Attribute public sealed class AspMvcAreaViewLocationFormatAttribute : Attribute
{ {
public AspMvcAreaViewLocationFormatAttribute([NotNull] string format) public AspMvcAreaViewLocationFormatAttribute([NotNull] string format)
...@@ -611,10 +730,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -611,10 +730,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
Format = format; Format = format;
} }
[NotNull] public string Format { get; private set; } [NotNull]
public string Format { get; }
} }
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple =
true)]
public sealed class AspMvcMasterLocationFormatAttribute : Attribute public sealed class AspMvcMasterLocationFormatAttribute : Attribute
{ {
public AspMvcMasterLocationFormatAttribute([NotNull] string format) public AspMvcMasterLocationFormatAttribute([NotNull] string format)
...@@ -622,10 +743,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -622,10 +743,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
Format = format; Format = format;
} }
[NotNull] public string Format { get; private set; } [NotNull]
public string Format { get; }
} }
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple =
true)]
public sealed class AspMvcPartialViewLocationFormatAttribute : Attribute public sealed class AspMvcPartialViewLocationFormatAttribute : Attribute
{ {
public AspMvcPartialViewLocationFormatAttribute([NotNull] string format) public AspMvcPartialViewLocationFormatAttribute([NotNull] string format)
...@@ -633,10 +756,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -633,10 +756,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
Format = format; Format = format;
} }
[NotNull] public string Format { get; private set; } [NotNull]
public string Format { get; }
} }
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple =
true)]
public sealed class AspMvcViewLocationFormatAttribute : Attribute public sealed class AspMvcViewLocationFormatAttribute : Attribute
{ {
public AspMvcViewLocationFormatAttribute([NotNull] string format) public AspMvcViewLocationFormatAttribute([NotNull] string format)
...@@ -644,7 +769,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -644,7 +769,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
Format = format; Format = format;
} }
[NotNull] public string Format { get; private set; } [NotNull]
public string Format { get; }
} }
/// <summary> /// <summary>
...@@ -656,14 +782,17 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -656,14 +782,17 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)]
public sealed class AspMvcActionAttribute : Attribute public sealed class AspMvcActionAttribute : Attribute
{ {
public AspMvcActionAttribute() { } public AspMvcActionAttribute()
{
}
public AspMvcActionAttribute([NotNull] string anonymousProperty) public AspMvcActionAttribute([NotNull] string anonymousProperty)
{ {
AnonymousProperty = anonymousProperty; AnonymousProperty = anonymousProperty;
} }
[CanBeNull] public string AnonymousProperty { get; private set; } [CanBeNull]
public string AnonymousProperty { get; }
} }
/// <summary> /// <summary>
...@@ -674,14 +803,17 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -674,14 +803,17 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
[AttributeUsage(AttributeTargets.Parameter)] [AttributeUsage(AttributeTargets.Parameter)]
public sealed class AspMvcAreaAttribute : Attribute public sealed class AspMvcAreaAttribute : Attribute
{ {
public AspMvcAreaAttribute() { } public AspMvcAreaAttribute()
{
}
public AspMvcAreaAttribute([NotNull] string anonymousProperty) public AspMvcAreaAttribute([NotNull] string anonymousProperty)
{ {
AnonymousProperty = anonymousProperty; AnonymousProperty = anonymousProperty;
} }
[CanBeNull] public string AnonymousProperty { get; private set; } [CanBeNull]
public string AnonymousProperty { get; }
} }
/// <summary> /// <summary>
...@@ -693,14 +825,17 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -693,14 +825,17 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)]
public sealed class AspMvcControllerAttribute : Attribute public sealed class AspMvcControllerAttribute : Attribute
{ {
public AspMvcControllerAttribute() { } public AspMvcControllerAttribute()
{
}
public AspMvcControllerAttribute([NotNull] string anonymousProperty) public AspMvcControllerAttribute([NotNull] string anonymousProperty)
{ {
AnonymousProperty = anonymousProperty; AnonymousProperty = anonymousProperty;
} }
[CanBeNull] public string AnonymousProperty { get; private set; } [CanBeNull]
public string AnonymousProperty { get; }
} }
/// <summary> /// <summary>
...@@ -708,14 +843,18 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -708,14 +843,18 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// for custom wrappers similar to <c>System.Web.Mvc.Controller.View(String, String)</c>. /// for custom wrappers similar to <c>System.Web.Mvc.Controller.View(String, String)</c>.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Parameter)] [AttributeUsage(AttributeTargets.Parameter)]
public sealed class AspMvcMasterAttribute : Attribute { } public sealed class AspMvcMasterAttribute : Attribute
{
}
/// <summary> /// <summary>
/// ASP.NET MVC attribute. Indicates that a parameter is an MVC model type. Use this attribute /// 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>. /// for custom wrappers similar to <c>System.Web.Mvc.Controller.View(String, Object)</c>.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Parameter)] [AttributeUsage(AttributeTargets.Parameter)]
public sealed class AspMvcModelTypeAttribute : Attribute { } public sealed class AspMvcModelTypeAttribute : Attribute
{
}
/// <summary> /// <summary>
/// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is an MVC /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is an MVC
...@@ -724,13 +863,17 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -724,13 +863,17 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// <c>System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String)</c>. /// <c>System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String)</c>.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)]
public sealed class AspMvcPartialViewAttribute : Attribute { } public sealed class AspMvcPartialViewAttribute : Attribute
{
}
/// <summary> /// <summary>
/// ASP.NET MVC attribute. Allows disabling inspections for MVC views within a class or a method. /// ASP.NET MVC attribute. Allows disabling inspections for MVC views within a class or a method.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class AspMvcSuppressViewErrorAttribute : Attribute { } public sealed class AspMvcSuppressViewErrorAttribute : Attribute
{
}
/// <summary> /// <summary>
/// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template.
...@@ -738,7 +881,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -738,7 +881,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// <c>System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String)</c>. /// <c>System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String)</c>.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Parameter)] [AttributeUsage(AttributeTargets.Parameter)]
public sealed class AspMvcDisplayTemplateAttribute : Attribute { } public sealed class AspMvcDisplayTemplateAttribute : Attribute
{
}
/// <summary> /// <summary>
/// ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template. /// ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template.
...@@ -746,7 +891,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -746,7 +891,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// <c>System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String)</c>. /// <c>System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String)</c>.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Parameter)] [AttributeUsage(AttributeTargets.Parameter)]
public sealed class AspMvcEditorTemplateAttribute : Attribute { } public sealed class AspMvcEditorTemplateAttribute : Attribute
{
}
/// <summary> /// <summary>
/// ASP.NET MVC attribute. Indicates that a parameter is an MVC template. /// ASP.NET MVC attribute. Indicates that a parameter is an MVC template.
...@@ -754,7 +901,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -754,7 +901,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// <c>System.ComponentModel.DataAnnotations.UIHintAttribute(System.String)</c>. /// <c>System.ComponentModel.DataAnnotations.UIHintAttribute(System.String)</c>.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Parameter)] [AttributeUsage(AttributeTargets.Parameter)]
public sealed class AspMvcTemplateAttribute : Attribute { } public sealed class AspMvcTemplateAttribute : Attribute
{
}
/// <summary> /// <summary>
/// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter
...@@ -763,47 +912,60 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -763,47 +912,60 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// <c>System.Web.Mvc.Controller.View(Object)</c>. /// <c>System.Web.Mvc.Controller.View(Object)</c>.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)]
public sealed class AspMvcViewAttribute : Attribute { } public sealed class AspMvcViewAttribute : Attribute
{
}
/// <summary> /// <summary>
/// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter
/// is an MVC view component name. /// is an MVC view component name.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Parameter)] [AttributeUsage(AttributeTargets.Parameter)]
public sealed class AspMvcViewComponentAttribute : Attribute { } public sealed class AspMvcViewComponentAttribute : Attribute
{
}
/// <summary> /// <summary>
/// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter /// 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. /// is an MVC view component view. If applied to a method, the MVC view component view name is default.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)]
public sealed class AspMvcViewComponentViewAttribute : Attribute { } public sealed class AspMvcViewComponentViewAttribute : Attribute
{
}
/// <summary> /// <summary>
/// ASP.NET MVC attribute. When applied to a parameter of an attribute, /// ASP.NET MVC attribute. When applied to a parameter of an attribute,
/// indicates that this parameter is an MVC action name. /// indicates that this parameter is an MVC action name.
/// </summary> /// </summary>
/// <example><code> /// <example>
/// <code>
/// [ActionName("Foo")] /// [ActionName("Foo")]
/// public ActionResult Login(string returnUrl) { /// public ActionResult Login(string returnUrl) {
/// ViewBag.ReturnUrl = Url.Action("Foo"); // OK /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK
/// return RedirectToAction("Bar"); // Error: Cannot resolve action /// return RedirectToAction("Bar"); // Error: Cannot resolve action
/// } /// }
/// </code></example> /// </code>
/// </example>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)]
public sealed class AspMvcActionSelectorAttribute : Attribute { } public sealed class AspMvcActionSelectorAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)]
public sealed class HtmlElementAttributesAttribute : Attribute public sealed class HtmlElementAttributesAttribute : Attribute
{ {
public HtmlElementAttributesAttribute() { } public HtmlElementAttributesAttribute()
{
}
public HtmlElementAttributesAttribute([NotNull] string name) public HtmlElementAttributesAttribute([NotNull] string name)
{ {
Name = name; Name = name;
} }
[CanBeNull] public string Name { get; private set; } [CanBeNull]
public string Name { get; }
} }
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)]
...@@ -814,7 +976,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -814,7 +976,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
Name = name; Name = name;
} }
[NotNull] public string Name { get; private set; } [NotNull]
public string Name { get; }
} }
/// <summary> /// <summary>
...@@ -823,7 +986,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -823,7 +986,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// <c>System.Web.WebPages.WebPageBase.RenderSection(String)</c>. /// <c>System.Web.WebPages.WebPageBase.RenderSection(String)</c>.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)]
public sealed class RazorSectionAttribute : Attribute { } public sealed class RazorSectionAttribute : Attribute
{
}
/// <summary> /// <summary>
/// Indicates how method, constructor invocation or property access /// Indicates how method, constructor invocation or property access
...@@ -837,7 +1002,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -837,7 +1002,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
CollectionAccessType = collectionAccessType; CollectionAccessType = collectionAccessType;
} }
public CollectionAccessType CollectionAccessType { get; private set; } public CollectionAccessType CollectionAccessType { get; }
} }
[Flags] [Flags]
...@@ -845,10 +1010,13 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -845,10 +1010,13 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
{ {
/// <summary>Method does not use or modify content of the collection.</summary> /// <summary>Method does not use or modify content of the collection.</summary>
None = 0, None = 0,
/// <summary>Method only reads content of the collection but does not modify it.</summary> /// <summary>Method only reads content of the collection but does not modify it.</summary>
Read = 1, Read = 1,
/// <summary>Method can change content of the collection but does not add new elements.</summary> /// <summary>Method can change content of the collection but does not add new elements.</summary>
ModifyExistingContent = 2, ModifyExistingContent = 2,
/// <summary>Method can add new elements to the collection.</summary> /// <summary>Method can add new elements to the collection.</summary>
UpdatedContent = ModifyExistingContent | 4 UpdatedContent = ModifyExistingContent | 4
} }
...@@ -856,14 +1024,16 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -856,14 +1024,16 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// <summary> /// <summary>
/// Indicates that the marked method is assertion method, i.e. it halts control flow if /// 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 /// one of the conditions is satisfied. To set the condition, mark one of the parameters with
/// <see cref="AssertionConditionAttribute"/> attribute. /// <see cref="AssertionConditionAttribute" /> attribute.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public sealed class AssertionMethodAttribute : Attribute { } public sealed class AssertionMethodAttribute : Attribute
{
}
/// <summary> /// <summary>
/// Indicates the condition parameter of the assertion method. The method itself should be /// Indicates the condition parameter of the assertion method. The method itself should be
/// marked by <see cref="AssertionMethodAttribute"/> attribute. The mandatory argument of /// marked by <see cref="AssertionMethodAttribute" /> attribute. The mandatory argument of
/// the attribute is the assertion type. /// the attribute is the assertion type.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Parameter)] [AttributeUsage(AttributeTargets.Parameter)]
...@@ -874,7 +1044,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -874,7 +1044,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
ConditionType = conditionType; ConditionType = conditionType;
} }
public AssertionConditionType ConditionType { get; private set; } public AssertionConditionType ConditionType { get; }
} }
/// <summary> /// <summary>
...@@ -885,12 +1055,15 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -885,12 +1055,15 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
{ {
/// <summary>Marked parameter should be evaluated to true.</summary> /// <summary>Marked parameter should be evaluated to true.</summary>
IS_TRUE = 0, IS_TRUE = 0,
/// <summary>Marked parameter should be evaluated to false.</summary> /// <summary>Marked parameter should be evaluated to false.</summary>
IS_FALSE = 1, IS_FALSE = 1,
/// <summary>Marked parameter should be evaluated to null value.</summary> /// <summary>Marked parameter should be evaluated to null value.</summary>
IS_NULL = 2, IS_NULL = 2,
/// <summary>Marked parameter should be evaluated to not null value.</summary> /// <summary>Marked parameter should be evaluated to not null value.</summary>
IS_NOT_NULL = 3, IS_NOT_NULL = 3
} }
/// <summary> /// <summary>
...@@ -899,7 +1072,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -899,7 +1072,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// </summary> /// </summary>
[Obsolete("Use [ContractAnnotation('=> halt')] instead")] [Obsolete("Use [ContractAnnotation('=> halt')] instead")]
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public sealed class TerminatesProgramAttribute : Attribute { } public sealed class TerminatesProgramAttribute : Attribute
{
}
/// <summary> /// <summary>
/// Indicates that method is pure LINQ method, with postponed enumeration (like Enumerable.Select, /// Indicates that method is pure LINQ method, with postponed enumeration (like Enumerable.Select,
...@@ -907,19 +1082,25 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -907,19 +1082,25 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// of delegate type by analyzing LINQ method chains. /// of delegate type by analyzing LINQ method chains.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public sealed class LinqTunnelAttribute : Attribute { } public sealed class LinqTunnelAttribute : Attribute
{
}
/// <summary> /// <summary>
/// Indicates that IEnumerable, passed as parameter, is not enumerated. /// Indicates that IEnumerable, passed as parameter, is not enumerated.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Parameter)] [AttributeUsage(AttributeTargets.Parameter)]
public sealed class NoEnumerationAttribute : Attribute { } public sealed class NoEnumerationAttribute : Attribute
{
}
/// <summary> /// <summary>
/// Indicates that parameter is regular expression pattern. /// Indicates that parameter is regular expression pattern.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Parameter)] [AttributeUsage(AttributeTargets.Parameter)]
public sealed class RegexPatternAttribute : Attribute { } public sealed class RegexPatternAttribute : Attribute
{
}
/// <summary> /// <summary>
/// Prevents the Member Reordering feature from tossing members of the marked class. /// Prevents the Member Reordering feature from tossing members of the marked class.
...@@ -929,14 +1110,18 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -929,14 +1110,18 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// </remarks> /// </remarks>
[AttributeUsage( [AttributeUsage(
AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum)] AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum)]
public sealed class NoReorderAttribute : Attribute { } public sealed class NoReorderAttribute : Attribute
{
}
/// <summary> /// <summary>
/// XAML attribute. Indicates the type that has <c>ItemsSource</c> property and should be treated /// 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. /// as <c>ItemsControl</c>-derived type, to enable inner items <c>DataContext</c> type resolve.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public sealed class XamlItemsControlAttribute : Attribute { } public sealed class XamlItemsControlAttribute : Attribute
{
}
/// <summary> /// <summary>
/// XAML attribute. Indicates the property of some <c>BindingBase</c>-derived type, that /// XAML attribute. Indicates the property of some <c>BindingBase</c>-derived type, that
...@@ -945,10 +1130,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -945,10 +1130,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Property should have the tree ancestor of the <c>ItemsControl</c> type or /// Property should have the tree ancestor of the <c>ItemsControl</c> type or
/// marked with the <see cref="XamlItemsControlAttribute"/> attribute. /// marked with the <see cref="XamlItemsControlAttribute" /> attribute.
/// </remarks> /// </remarks>
[AttributeUsage(AttributeTargets.Property)] [AttributeUsage(AttributeTargets.Property)]
public sealed class XamlItemBindingOfItemsControlAttribute : Attribute { } public sealed class XamlItemBindingOfItemsControlAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class AspChildControlTypeAttribute : Attribute public sealed class AspChildControlTypeAttribute : Attribute
...@@ -959,19 +1146,27 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -959,19 +1146,27 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
ControlType = controlType; ControlType = controlType;
} }
[NotNull] public string TagName { get; private set; } [NotNull]
public string TagName { get; }
[NotNull] public Type ControlType { get; private set; } [NotNull]
public Type ControlType { get; }
} }
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]
public sealed class AspDataFieldAttribute : Attribute { } public sealed class AspDataFieldAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]
public sealed class AspDataFieldsAttribute : Attribute { } public sealed class AspDataFieldsAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Property)] [AttributeUsage(AttributeTargets.Property)]
public sealed class AspMethodPropertyAttribute : Attribute { } public sealed class AspMethodPropertyAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class AspRequiredAttributeAttribute : Attribute public sealed class AspRequiredAttributeAttribute : Attribute
...@@ -981,18 +1176,19 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -981,18 +1176,19 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
Attribute = attribute; Attribute = attribute;
} }
[NotNull] public string Attribute { get; private set; } [NotNull]
public string Attribute { get; }
} }
[AttributeUsage(AttributeTargets.Property)] [AttributeUsage(AttributeTargets.Property)]
public sealed class AspTypePropertyAttribute : Attribute public sealed class AspTypePropertyAttribute : Attribute
{ {
public bool CreateConstructorReferences { get; private set; }
public AspTypePropertyAttribute(bool createConstructorReferences) public AspTypePropertyAttribute(bool createConstructorReferences)
{ {
CreateConstructorReferences = createConstructorReferences; CreateConstructorReferences = createConstructorReferences;
} }
public bool CreateConstructorReferences { get; }
} }
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
...@@ -1003,7 +1199,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -1003,7 +1199,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
Name = name; Name = name;
} }
[NotNull] public string Name { get; private set; } [NotNull]
public string Name { get; }
} }
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
...@@ -1015,9 +1212,11 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -1015,9 +1212,11 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
FieldName = fieldName; FieldName = fieldName;
} }
[NotNull] public string Type { get; private set; } [NotNull]
public string Type { get; }
[NotNull] public string FieldName { get; private set; } [NotNull]
public string FieldName { get; }
} }
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
...@@ -1028,21 +1227,32 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations ...@@ -1028,21 +1227,32 @@ namespace Titanium.Web.Proxy.Examples.Wpf.Annotations
Directive = directive; Directive = directive;
} }
[NotNull] public string Directive { get; private set; } [NotNull]
public string Directive { get; }
} }
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public sealed class RazorHelperCommonAttribute : Attribute { } public sealed class RazorHelperCommonAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Property)] [AttributeUsage(AttributeTargets.Property)]
public sealed class RazorLayoutAttribute : Attribute { } public sealed class RazorLayoutAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public sealed class RazorWriteLiteralMethodAttribute : Attribute { } public sealed class RazorWriteLiteralMethodAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public sealed class RazorWriteMethodAttribute : Attribute { } public sealed class RazorWriteMethodAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Parameter)] [AttributeUsage(AttributeTargets.Parameter)]
public sealed class RazorWriteMethodParameterAttribute : Attribute { } public sealed class RazorWriteMethodParameterAttribute : Attribute
{
}
} }
using System.Reflection; using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Windows; using System.Windows;
......
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)"> <SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles> <Profiles>
<Profile Name="(Default)" /> <Profile Name="(Default)" />
......
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Examples.Wpf.Annotations; using Titanium.Web.Proxy.Examples.Wpf.Annotations;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
...@@ -9,15 +8,15 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -9,15 +8,15 @@ namespace Titanium.Web.Proxy.Examples.Wpf
{ {
public class SessionListItem : INotifyPropertyChanged public class SessionListItem : INotifyPropertyChanged
{ {
private string statusCode;
private string protocol;
private string host;
private string url;
private long? bodySize; private long? bodySize;
private Exception exception;
private string host;
private string process; private string process;
private string protocol;
private long receivedDataCount; private long receivedDataCount;
private long sentDataCount; private long sentDataCount;
private Exception exception; private string statusCode;
private string url;
public int Number { get; set; } public int Number { get; set; }
...@@ -81,7 +80,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -81,7 +80,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
protected void SetField<T>(ref T field, T value,[CallerMemberName] string propertyName = null) protected void SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{ {
if (!Equals(field, value)) if (!Equals(field, value))
{ {
......
...@@ -51,8 +51,8 @@ ...@@ -51,8 +51,8 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="StreamExtended, Version=1.0.141.0, Culture=neutral, PublicKeyToken=bbfa0f1d54f50043, processorArchitecture=MSIL"> <Reference Include="StreamExtended, Version=1.0.147.0, Culture=neutral, PublicKeyToken=bbfa0f1d54f50043, processorArchitecture=MSIL">
<HintPath>..\..\packages\StreamExtended.1.0.141-beta\lib\net45\StreamExtended.dll</HintPath> <HintPath>..\..\packages\StreamExtended.1.0.147-beta\lib\net45\StreamExtended.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="StreamExtended" version="1.0.141-beta" targetFramework="net45" /> <package id="StreamExtended" version="1.0.147-beta" targetFramework="net45" />
</packages> </packages>
\ No newline at end of file
...@@ -35,7 +35,7 @@ namespace Titanium.Web.Proxy.IntegrationTests ...@@ -35,7 +35,7 @@ namespace Titanium.Web.Proxy.IntegrationTests
var handler = new HttpClientHandler var handler = new HttpClientHandler
{ {
Proxy = new WebProxy($"http://localhost:{localProxyPort}", false), Proxy = new WebProxy($"http://localhost:{localProxyPort}", false),
UseProxy = true, UseProxy = true
}; };
var client = new HttpClient(handler); var client = new HttpClient(handler);
...@@ -68,9 +68,11 @@ namespace Titanium.Web.Proxy.IntegrationTests ...@@ -68,9 +68,11 @@ namespace Titanium.Web.Proxy.IntegrationTests
proxyServer.Start(); proxyServer.Start();
foreach (var endPoint in proxyServer.ProxyEndPoints) foreach (var endPoint in proxyServer.ProxyEndPoints)
{
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ", Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port); endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
} }
}
public void Stop() public void Stop()
{ {
......
...@@ -27,6 +27,7 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -27,6 +27,7 @@ namespace Titanium.Web.Proxy.UnitTests
mgr.CertificateEngine = CertificateEngine.BouncyCastle; mgr.CertificateEngine = CertificateEngine.BouncyCastle;
mgr.ClearIdleCertificates(); mgr.ClearIdleCertificates();
for (int i = 0; i < 5; i++) for (int i = 0; i < 5; i++)
{
foreach (string host in hostNames) foreach (string host in hostNames)
{ {
tasks.Add(Task.Run(() => tasks.Add(Task.Run(() =>
...@@ -36,6 +37,7 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -36,6 +37,7 @@ namespace Titanium.Web.Proxy.UnitTests
Assert.IsNotNull(certificate); Assert.IsNotNull(certificate);
})); }));
} }
}
await Task.WhenAll(tasks.ToArray()); await Task.WhenAll(tasks.ToArray());
...@@ -59,6 +61,7 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -59,6 +61,7 @@ namespace Titanium.Web.Proxy.UnitTests
mgr.ClearIdleCertificates(); mgr.ClearIdleCertificates();
for (int i = 0; i < 5; i++) for (int i = 0; i < 5; i++)
{
foreach (string host in hostNames) foreach (string host in hostNames)
{ {
tasks.Add(Task.Run(() => tasks.Add(Task.Run(() =>
...@@ -68,6 +71,7 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -68,6 +71,7 @@ namespace Titanium.Web.Proxy.UnitTests
Assert.IsNotNull(certificate); Assert.IsNotNull(certificate);
})); }));
} }
}
await Task.WhenAll(tasks.ToArray()); await Task.WhenAll(tasks.ToArray());
mgr.RemoveTrustedRootCertificate(true); mgr.RemoveTrustedRootCertificate(true);
......
...@@ -9,7 +9,8 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -9,7 +9,8 @@ namespace Titanium.Web.Proxy.UnitTests
public class ProxyServerTests public class ProxyServerTests
{ {
[TestMethod] [TestMethod]
public void GivenOneEndpointIsAlreadyAddedToAddress_WhenAddingNewEndpointToExistingAddress_ThenExceptionIsThrown() public void
GivenOneEndpointIsAlreadyAddedToAddress_WhenAddingNewEndpointToExistingAddress_ThenExceptionIsThrown()
{ {
// Arrange // Arrange
var proxy = new ProxyServer(); var proxy = new ProxyServer();
...@@ -34,7 +35,8 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -34,7 +35,8 @@ namespace Titanium.Web.Proxy.UnitTests
} }
[TestMethod] [TestMethod]
public void GivenOneEndpointIsAlreadyAddedToAddress_WhenAddingNewEndpointToExistingAddress_ThenTwoEndpointsExists() public void
GivenOneEndpointIsAlreadyAddedToAddress_WhenAddingNewEndpointToExistingAddress_ThenTwoEndpointsExists()
{ {
// Arrange // Arrange
var proxy = new ProxyServer(); var proxy = new ProxyServer();
...@@ -74,7 +76,8 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -74,7 +76,8 @@ namespace Titanium.Web.Proxy.UnitTests
} }
[TestMethod] [TestMethod]
public void GivenOneEndpointIsAlreadyAddedToZeroPort_WhenAddingNewEndpointToExistingPort_ThenTwoEndpointsExists() public void
GivenOneEndpointIsAlreadyAddedToZeroPort_WhenAddingNewEndpointToExistingPort_ThenTwoEndpointsExists()
{ {
// Arrange // Arrange
var proxy = new ProxyServer(); var proxy = new ProxyServer();
......
...@@ -85,7 +85,9 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -85,7 +85,9 @@ namespace Titanium.Web.Proxy.UnitTests
{ {
hostName = Dns.GetHostName(); hostName = Dns.GetHostName();
} }
catch{} catch
{
}
if (hostName != null) if (hostName != null)
{ {
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SIMPLE_EMBEDDED_STATEMENT_STYLE/@EntryValue">LINE_BREAK</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SIMPLE_EMBEDDED_STATEMENT_STYLE/@EntryValue">LINE_BREAK</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AFTER_TYPECAST_PARENTHESES/@EntryValue">False</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AFTER_TYPECAST_PARENTHESES/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_SINGLE_LINE_ARRAY_INITIALIZER_BRACES/@EntryValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_SINGLE_LINE_ARRAY_INITIALIZER_BRACES/@EntryValue">True</s:Boolean>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">160</s:Int64> <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">120</s:Int64>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseExplicitType</s:String> <s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseExplicitType</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseVar</s:String> <s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseVar</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BC/@EntryIndexedValue">BC</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BC/@EntryIndexedValue">BC</s:String>
......
...@@ -16,7 +16,8 @@ namespace Titanium.Web.Proxy ...@@ -16,7 +16,8 @@ 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(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) internal bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{ {
//if user callback is registered then do it //if user callback is registered then do it
if (ServerCertificateValidationCallback != null) if (ServerCertificateValidationCallback != null)
...@@ -52,14 +53,15 @@ namespace Titanium.Web.Proxy ...@@ -52,14 +53,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(object sender, string targetHost, X509CertificateCollection localCertificates, internal X509Certificate SelectClientCertificate(object sender, string targetHost,
X509CertificateCollection localCertificates,
X509Certificate remoteCertificate, string[] acceptableIssuers) X509Certificate remoteCertificate, string[] acceptableIssuers)
{ {
X509Certificate clientCertificate = null; X509Certificate clientCertificate = null;
if (acceptableIssuers != null && acceptableIssuers.Length > 0 && localCertificates != null && localCertificates.Count > 0) if (acceptableIssuers != null && acceptableIssuers.Length > 0 && localCertificates != null &&
localCertificates.Count > 0)
{ {
// Use the first certificate that is from an acceptable issuer.
foreach (var certificate in localCertificates) foreach (var certificate in localCertificates)
{ {
string issuer = certificate.Issuer; string issuer = certificate.Issuer;
......
...@@ -9,17 +9,17 @@ namespace Titanium.Web.Proxy.Compression ...@@ -9,17 +9,17 @@ namespace Titanium.Web.Proxy.Compression
internal static class CompressionFactory internal static class CompressionFactory
{ {
//cache //cache
private static readonly Lazy<ICompression> gzip = new Lazy<ICompression>(() => new GZipCompression()); private static readonly ICompression gzip = new GZipCompression();
private static readonly Lazy<ICompression> deflate = new Lazy<ICompression>(() => new DeflateCompression()); private static readonly ICompression deflate = new DeflateCompression();
public static ICompression GetCompression(string type) public static ICompression GetCompression(string type)
{ {
switch (type) switch (type)
{ {
case KnownHeaders.ContentEncodingGzip: case KnownHeaders.ContentEncodingGzip:
return gzip.Value; return gzip;
case KnownHeaders.ContentEncodingDeflate: case KnownHeaders.ContentEncodingDeflate:
return deflate.Value; return deflate;
default: default:
throw new Exception($"Unsupported compression mode: {type}"); throw new Exception($"Unsupported compression mode: {type}");
} }
......
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression namespace Titanium.Web.Proxy.Compression
{ {
......
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression namespace Titanium.Web.Proxy.Compression
{ {
......
using System.IO; using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression namespace Titanium.Web.Proxy.Compression
{ {
/// <summary> /// <summary>
/// An inteface for http compression /// An inteface for http compression
/// </summary> /// </summary>
interface ICompression internal interface ICompression
{ {
Stream GetStream(Stream stream); Stream GetStream(Stream stream);
} }
......
...@@ -9,17 +9,18 @@ namespace Titanium.Web.Proxy.Decompression ...@@ -9,17 +9,18 @@ namespace Titanium.Web.Proxy.Decompression
internal class DecompressionFactory internal class DecompressionFactory
{ {
//cache //cache
private static readonly Lazy<IDecompression> gzip = new Lazy<IDecompression>(() => new GZipDecompression()); private static readonly IDecompression gzip = new GZipDecompression();
private static readonly Lazy<IDecompression> deflate = new Lazy<IDecompression>(() => new DeflateDecompression());
private static readonly IDecompression deflate = new DeflateDecompression();
public static IDecompression Create(string type) public static IDecompression Create(string type)
{ {
switch (type) switch (type)
{ {
case KnownHeaders.ContentEncodingGzip: case KnownHeaders.ContentEncodingGzip:
return gzip.Value; return gzip;
case KnownHeaders.ContentEncodingDeflate: case KnownHeaders.ContentEncodingDeflate:
return deflate.Value; return deflate;
default: default:
throw new Exception($"Unsupported decompression mode: {type}"); throw new Exception($"Unsupported decompression mode: {type}");
} }
......
using StreamExtended.Helpers; using System.IO;
using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
......
using StreamExtended.Helpers; using System.IO;
using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
......
using System.IO; using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
......
using System; using System.Threading.Tasks;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
public delegate Task AsyncEventHandler<TEventArgs>(object sender, TEventArgs e); public delegate Task AsyncEventHandler<in TEventArgs>(object sender, TEventArgs e);
} }
using System;
using System.Threading;
namespace Titanium.Web.Proxy.EventArguments
{
public class BeforeSslAuthenticateEventArgs : EventArgs
{
internal readonly CancellationTokenSource TaskCancellationSource;
internal BeforeSslAuthenticateEventArgs(CancellationTokenSource taskCancellationSource)
{
TaskCancellationSource = taskCancellationSource;
}
public string SniHostName { get; internal set; }
public bool DecryptSsl { get; set; } = true;
public void TerminateSession()
{
TaskCancellationSource.Cancel();
}
}
}
...@@ -4,17 +4,17 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -4,17 +4,17 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
public class DataEventArgs : EventArgs public class DataEventArgs : EventArgs
{ {
public byte[] Buffer { get; } internal DataEventArgs(byte[] buffer, int offset, int count)
public int Offset { get; }
public int Count { get; }
public DataEventArgs(byte[] buffer, int offset, int count)
{ {
Buffer = buffer; Buffer = buffer;
Offset = offset; Offset = offset;
Count = count; Count = count;
} }
public byte[] Buffer { get; }
public int Offset { get; }
public int Count { get; }
} }
} }
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
...@@ -10,14 +9,15 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -10,14 +9,15 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
internal class LimitedStream : Stream internal class LimitedStream : Stream
{ {
private readonly CustomBufferedStream baseStream;
private readonly CustomBinaryReader baseReader; private readonly CustomBinaryReader baseReader;
private readonly CustomBufferedStream baseStream;
private readonly bool isChunked; private readonly bool isChunked;
private long bytesRemaining;
private bool readChunkTrail; private bool readChunkTrail;
private long bytesRemaining;
public LimitedStream(CustomBufferedStream baseStream, CustomBinaryReader baseReader, bool isChunked, long contentLength) internal LimitedStream(CustomBufferedStream baseStream, CustomBinaryReader baseReader, bool isChunked,
long contentLength)
{ {
this.baseStream = baseStream; this.baseStream = baseStream;
this.baseReader = baseReader; this.baseReader = baseReader;
...@@ -29,6 +29,20 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -29,6 +29,20 @@ namespace Titanium.Web.Proxy.EventArguments
: contentLength; : contentLength;
} }
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => throw new NotSupportedException();
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
private void GetNextChunk() private void GetNextChunk()
{ {
if (readChunkTrail) if (readChunkTrail)
...@@ -43,7 +57,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -43,7 +57,6 @@ namespace Titanium.Web.Proxy.EventArguments
int idx = chunkHead.IndexOf(";"); int idx = chunkHead.IndexOf(";");
if (idx >= 0) if (idx >= 0)
{ {
// remove chunk extension
chunkHead = chunkHead.Substring(0, idx); chunkHead = chunkHead.Substring(0, idx);
} }
...@@ -134,19 +147,5 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -134,19 +147,5 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => throw new NotSupportedException();
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
} }
} }
...@@ -5,14 +5,14 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -5,14 +5,14 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
public class MultipartRequestPartSentEventArgs : EventArgs public class MultipartRequestPartSentEventArgs : EventArgs
{ {
public string Boundary { get; }
public HeaderCollection Headers { get; }
public MultipartRequestPartSentEventArgs(string boundary, HeaderCollection headers) public MultipartRequestPartSentEventArgs(string boundary, HeaderCollection headers)
{ {
Boundary = boundary; Boundary = boundary;
Headers = headers; Headers = headers;
} }
public string Boundary { get; }
public HeaderCollection Headers { get; }
} }
} }
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Reflection; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended.Helpers; using StreamExtended.Helpers;
using StreamExtended.Network; using StreamExtended.Network;
...@@ -12,7 +11,6 @@ using Titanium.Web.Proxy.Helpers; ...@@ -12,7 +11,6 @@ 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;
using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
...@@ -31,6 +29,21 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -31,6 +29,21 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
private bool reRequest; private bool reRequest;
/// <summary>
/// Constructor to initialize the proxy
/// </summary>
internal SessionEventArgs(int bufferSize, ProxyEndPoint endPoint,
CancellationTokenSource cancellationTokenSource, ExceptionHandler exceptionFunc)
: this(bufferSize, endPoint, null, cancellationTokenSource, exceptionFunc)
{
}
protected SessionEventArgs(int bufferSize, ProxyEndPoint endPoint,
Request request, CancellationTokenSource cancellationTokenSource, ExceptionHandler exceptionFunc)
: base(bufferSize, endPoint, cancellationTokenSource, request, exceptionFunc)
{
}
private bool hasMulipartEventSubscribers => MultipartRequestPartSent != null; private bool hasMulipartEventSubscribers => MultipartRequestPartSent != null;
/// <summary> /// <summary>
...@@ -55,19 +68,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -55,19 +68,6 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
public event EventHandler<MultipartRequestPartSentEventArgs> MultipartRequestPartSent; public event EventHandler<MultipartRequestPartSentEventArgs> MultipartRequestPartSent;
/// <summary>
/// Constructor to initialize the proxy
/// </summary>
internal SessionEventArgs(int bufferSize, ProxyEndPoint endPoint, ExceptionHandler exceptionFunc)
: this(bufferSize, endPoint, exceptionFunc, null)
{
}
protected SessionEventArgs(int bufferSize, ProxyEndPoint endPoint, ExceptionHandler exceptionFunc, Request request)
: base(bufferSize, endPoint, exceptionFunc, request)
{
}
private CustomBufferedStream GetStream(bool isRequest) private CustomBufferedStream GetStream(bool isRequest)
{ {
return isRequest ? ProxyClient.ClientStream : WebSession.ServerConnection.Stream; return isRequest ? ProxyClient.ClientStream : WebSession.ServerConnection.Stream;
...@@ -86,7 +86,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -86,7 +86,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary> /// <summary>
/// Read request body content as bytes[] for current session /// Read request body content as bytes[] for current session
/// </summary> /// </summary>
private async Task ReadRequestBodyAsync() private async Task ReadRequestBodyAsync(CancellationToken cancellationToken)
{ {
WebSession.Request.EnsureBodyAvailable(false); WebSession.Request.EnsureBodyAvailable(false);
...@@ -95,7 +95,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -95,7 +95,7 @@ namespace Titanium.Web.Proxy.EventArguments
//If not already read (not cached yet) //If not already read (not cached yet)
if (!request.IsBodyRead) if (!request.IsBodyRead)
{ {
var body = await ReadBodyAsync(true); var body = await ReadBodyAsync(true, cancellationToken);
request.Body = body; request.Body = body;
//Now set the flag to true //Now set the flag to true
...@@ -108,10 +108,10 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -108,10 +108,10 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary> /// <summary>
/// reinit response object /// reinit response object
/// </summary> /// </summary>
internal async Task ClearResponse() internal async Task ClearResponse(CancellationToken cancellationToken)
{ {
//syphon out the response body from server //syphon out the response body from server
await SyphonOutBodyAsync(false); await SyphonOutBodyAsync(false, cancellationToken);
WebSession.Response = new Response(); WebSession.Response = new Response();
} }
...@@ -130,7 +130,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -130,7 +130,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary> /// <summary>
/// Read response body as byte[] for current response /// Read response body as byte[] for current response
/// </summary> /// </summary>
private async Task ReadResponseBodyAsync() private async Task ReadResponseBodyAsync(CancellationToken cancellationToken)
{ {
if (!WebSession.Request.Locked) if (!WebSession.Request.Locked)
{ {
...@@ -146,7 +146,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -146,7 +146,7 @@ namespace Titanium.Web.Proxy.EventArguments
//If not already read (not cached yet) //If not already read (not cached yet)
if (!response.IsBodyRead) if (!response.IsBodyRead)
{ {
var body = await ReadBodyAsync(false); var body = await ReadBodyAsync(false, cancellationToken);
response.Body = body; response.Body = body;
//Now set the flag to true //Now set the flag to true
...@@ -156,7 +156,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -156,7 +156,7 @@ namespace Titanium.Web.Proxy.EventArguments
} }
} }
private async Task<byte[]> ReadBodyAsync(bool isRequest) private async Task<byte[]> ReadBodyAsync(bool isRequest, CancellationToken cancellationToken)
{ {
using (var bodyStream = new MemoryStream()) using (var bodyStream = new MemoryStream())
{ {
...@@ -164,18 +164,18 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -164,18 +164,18 @@ namespace Titanium.Web.Proxy.EventArguments
if (isRequest) if (isRequest)
{ {
await CopyRequestBodyAsync(writer, TransformationMode.Uncompress); await CopyRequestBodyAsync(writer, TransformationMode.Uncompress, cancellationToken);
} }
else else
{ {
await CopyResponseBodyAsync(writer, TransformationMode.Uncompress); await CopyResponseBodyAsync(writer, TransformationMode.Uncompress, cancellationToken);
} }
return bodyStream.ToArray(); return bodyStream.ToArray();
} }
} }
internal async Task SyphonOutBodyAsync(bool isRequest) internal async Task SyphonOutBodyAsync(bool isRequest, CancellationToken cancellationToken)
{ {
var requestResponse = isRequest ? (RequestResponseBase)WebSession.Request : WebSession.Response; var requestResponse = isRequest ? (RequestResponseBase)WebSession.Request : WebSession.Response;
if (requestResponse.IsBodyRead || !requestResponse.OriginalHasBody) if (requestResponse.IsBodyRead || !requestResponse.OriginalHasBody)
...@@ -186,7 +186,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -186,7 +186,7 @@ namespace Titanium.Web.Proxy.EventArguments
using (var bodyStream = new MemoryStream()) using (var bodyStream = new MemoryStream())
{ {
var writer = new HttpWriter(bodyStream, BufferSize); var writer = new HttpWriter(bodyStream, BufferSize);
await CopyBodyAsync(isRequest, writer, TransformationMode.None, null); await CopyBodyAsync(isRequest, writer, TransformationMode.None, null, cancellationToken);
} }
} }
...@@ -194,7 +194,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -194,7 +194,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// This is called when the request is PUT/POST/PATCH to read the body /// This is called when the request is PUT/POST/PATCH to read the body
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
internal async Task CopyRequestBodyAsync(HttpWriter writer, TransformationMode transformation) internal async Task CopyRequestBodyAsync(HttpWriter writer, TransformationMode transformation, CancellationToken cancellationToken)
{ {
var request = WebSession.Request; var request = WebSession.Request;
...@@ -211,7 +211,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -211,7 +211,7 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
while (contentLength > copyStream.ReadBytes) while (contentLength > copyStream.ReadBytes)
{ {
long read = await ReadUntilBoundaryAsync(copyStreamReader, contentLength, boundary); long read = await ReadUntilBoundaryAsync(copyStreamReader, contentLength, boundary, cancellationToken);
if (read == 0) if (read == 0)
{ {
break; break;
...@@ -220,26 +220,26 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -220,26 +220,26 @@ namespace Titanium.Web.Proxy.EventArguments
if (contentLength > copyStream.ReadBytes) if (contentLength > copyStream.ReadBytes)
{ {
var headers = new HeaderCollection(); var headers = new HeaderCollection();
await HeaderParser.ReadHeaders(copyStreamReader, headers); await HeaderParser.ReadHeaders(copyStreamReader, headers, cancellationToken);
OnMultipartRequestPartSent(boundary, headers); OnMultipartRequestPartSent(boundary, headers);
} }
} }
await copyStream.FlushAsync(); await copyStream.FlushAsync(cancellationToken);
} }
} }
else else
{ {
await CopyBodyAsync(true, writer, transformation, OnDataSent); await CopyBodyAsync(true, writer, transformation, OnDataSent, cancellationToken);
} }
} }
internal async Task CopyResponseBodyAsync(HttpWriter writer, TransformationMode transformation) internal async Task CopyResponseBodyAsync(HttpWriter writer, TransformationMode transformation, CancellationToken cancellationToken)
{ {
await CopyBodyAsync(false, writer, transformation, OnDataReceived); await CopyBodyAsync(false, writer, transformation, OnDataReceived, cancellationToken);
} }
private async Task CopyBodyAsync(bool isRequest, HttpWriter writer, TransformationMode transformation, Action<byte[], int, int> onCopy) private async Task CopyBodyAsync(bool isRequest, HttpWriter writer, TransformationMode transformation, Action<byte[], int, int> onCopy, CancellationToken cancellationToken)
{ {
var stream = GetStream(isRequest); var stream = GetStream(isRequest);
var reader = GetStreamReader(isRequest); var reader = GetStreamReader(isRequest);
...@@ -250,7 +250,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -250,7 +250,7 @@ namespace Titanium.Web.Proxy.EventArguments
long contentLength = requestResponse.ContentLength; long contentLength = requestResponse.ContentLength;
if (transformation == TransformationMode.None) if (transformation == TransformationMode.None)
{ {
await writer.CopyBodyAsync(reader, isChunked, contentLength, onCopy); await writer.CopyBodyAsync(reader, isChunked, contentLength, onCopy, cancellationToken);
return; return;
} }
...@@ -271,7 +271,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -271,7 +271,7 @@ namespace Titanium.Web.Proxy.EventArguments
var bufStream = new CustomBufferedStream(s, BufferSize, true); var bufStream = new CustomBufferedStream(s, BufferSize, true);
reader = new CustomBinaryReader(bufStream, BufferSize); reader = new CustomBinaryReader(bufStream, BufferSize);
await writer.CopyBodyAsync(reader, false, -1, onCopy); await writer.CopyBodyAsync(reader, false, -1, onCopy, cancellationToken);
} }
finally finally
{ {
...@@ -287,7 +287,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -287,7 +287,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// Read a line from the byte stream /// Read a line from the byte stream
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
private async Task<long> ReadUntilBoundaryAsync(CustomBinaryReader reader, long totalBytesToRead, string boundary) private async Task<long> ReadUntilBoundaryAsync(CustomBinaryReader reader, long totalBytesToRead, string boundary, CancellationToken cancellationToken)
{ {
int bufferDataLength = 0; int bufferDataLength = 0;
...@@ -297,7 +297,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -297,7 +297,7 @@ namespace Titanium.Web.Proxy.EventArguments
int boundaryLength = boundary.Length + 4; int boundaryLength = boundary.Length + 4;
long bytesRead = 0; long bytesRead = 0;
while (bytesRead < totalBytesToRead && (reader.DataAvailable || await reader.FillBufferAsync())) while (bytesRead < totalBytesToRead && (reader.DataAvailable || await reader.FillBufferAsync(cancellationToken)))
{ {
byte newChar = reader.ReadByteFromBuffer(); byte newChar = reader.ReadByteFromBuffer();
buffer[bufferDataLength] = newChar; buffer[bufferDataLength] = newChar;
...@@ -349,11 +349,11 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -349,11 +349,11 @@ namespace Titanium.Web.Proxy.EventArguments
/// Gets the request body as bytes /// Gets the request body as bytes
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public async Task<byte[]> GetRequestBody() public async Task<byte[]> GetRequestBody(CancellationToken cancellationToken = default)
{ {
if (!WebSession.Request.IsBodyRead) if (!WebSession.Request.IsBodyRead)
{ {
await ReadRequestBodyAsync(); await ReadRequestBodyAsync(cancellationToken);
} }
return WebSession.Request.Body; return WebSession.Request.Body;
...@@ -363,11 +363,11 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -363,11 +363,11 @@ namespace Titanium.Web.Proxy.EventArguments
/// Gets the request body as string /// Gets the request body as string
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public async Task<string> GetRequestBodyAsString() public async Task<string> GetRequestBodyAsString(CancellationToken cancellationToken = default)
{ {
if (!WebSession.Request.IsBodyRead) if (!WebSession.Request.IsBodyRead)
{ {
await ReadRequestBodyAsync(); await ReadRequestBodyAsync(cancellationToken);
} }
return WebSession.Request.BodyString; return WebSession.Request.BodyString;
...@@ -406,11 +406,11 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -406,11 +406,11 @@ namespace Titanium.Web.Proxy.EventArguments
/// Gets the response body as byte array /// Gets the response body as byte array
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public async Task<byte[]> GetResponseBody() public async Task<byte[]> GetResponseBody(CancellationToken cancellationToken = default)
{ {
if (!WebSession.Response.IsBodyRead) if (!WebSession.Response.IsBodyRead)
{ {
await ReadResponseBodyAsync(); await ReadResponseBodyAsync(cancellationToken);
} }
return WebSession.Response.Body; return WebSession.Response.Body;
...@@ -420,11 +420,11 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -420,11 +420,11 @@ namespace Titanium.Web.Proxy.EventArguments
/// Gets the response body as string /// Gets the response body as string
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public async Task<string> GetResponseBodyAsString() public async Task<string> GetResponseBodyAsString(CancellationToken cancellationToken = default)
{ {
if (!WebSession.Response.IsBodyRead) if (!WebSession.Response.IsBodyRead)
{ {
await ReadResponseBodyAsync(); await ReadResponseBodyAsync(cancellationToken);
} }
return WebSession.Response.BodyString; return WebSession.Response.BodyString;
......
using System; using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net; using System.Net;
using System.Reflection; using System.Threading;
using System.Threading.Tasks;
using StreamExtended.Helpers;
using StreamExtended.Network;
using Titanium.Web.Proxy.Decompression;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Http.Responses;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Network;
...@@ -22,7 +14,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -22,7 +14,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// A proxy session ends when client terminates connection to proxy /// A proxy session ends when client terminates connection to proxy
/// or when server terminates connection from proxy /// or when server terminates connection from proxy
/// </summary> /// </summary>
public class SessionEventArgsBase : EventArgs, IDisposable public abstract class SessionEventArgsBase : EventArgs, IDisposable
{ {
/// <summary> /// <summary>
/// Size of Buffers used by this object /// Size of Buffers used by this object
...@@ -31,6 +23,50 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -31,6 +23,50 @@ namespace Titanium.Web.Proxy.EventArguments
protected readonly ExceptionHandler ExceptionFunc; protected readonly ExceptionHandler ExceptionFunc;
internal readonly CancellationTokenSource CancellationTokenSource;
/// <summary>
/// Constructor to initialize the proxy
/// </summary>
internal SessionEventArgsBase(int bufferSize, ProxyEndPoint endPoint,
CancellationTokenSource cancellationTokenSource, ExceptionHandler exceptionFunc)
: this(bufferSize, endPoint, cancellationTokenSource, null, exceptionFunc)
{
}
protected SessionEventArgsBase(int bufferSize, ProxyEndPoint endPoint,
CancellationTokenSource cancellationTokenSource,
Request request, ExceptionHandler exceptionFunc)
{
BufferSize = bufferSize;
ExceptionFunc = exceptionFunc;
CancellationTokenSource = cancellationTokenSource;
ProxyClient = new ProxyClient();
WebSession = new HttpWebClient(bufferSize, request);
LocalEndPoint = endPoint;
WebSession.ProcessId = new Lazy<int>(() =>
{
if (RunTime.IsWindows)
{
var remoteEndPoint = (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint;
//If client is localhost get the process id
if (NetworkHelper.IsLocalIpAddress(remoteEndPoint.Address))
{
var ipVersion = endPoint.IpV6Enabled ? IpVersion.Ipv6 : IpVersion.Ipv4;
return TcpHelper.GetProcessIdByLocalPort(ipVersion, remoteEndPoint.Port);
}
//can't access process Id of remote request from remote machine
return -1;
}
throw new PlatformNotSupportedException();
});
}
/// <summary> /// <summary>
/// Holds a reference to client /// Holds a reference to client
/// </summary> /// </summary>
...@@ -63,10 +99,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -63,10 +99,6 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
public ExternalProxy CustomUpStreamProxyUsed { get; internal set; } public ExternalProxy CustomUpStreamProxyUsed { get; internal set; }
public event EventHandler<DataEventArgs> DataSent;
public event EventHandler<DataEventArgs> DataReceived;
public ProxyEndPoint LocalEndPoint { get; } public ProxyEndPoint LocalEndPoint { get; }
public bool IsTransparent => LocalEndPoint is TransparentProxyEndPoint; public bool IsTransparent => LocalEndPoint is TransparentProxyEndPoint;
...@@ -74,41 +106,22 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -74,41 +106,22 @@ namespace Titanium.Web.Proxy.EventArguments
public Exception Exception { get; internal set; } public Exception Exception { get; internal set; }
/// <summary> /// <summary>
/// Constructor to initialize the proxy /// implement any cleanup here
/// </summary> /// </summary>
internal SessionEventArgsBase(int bufferSize, ProxyEndPoint endPoint, ExceptionHandler exceptionFunc) public virtual void Dispose()
: this(bufferSize, endPoint, exceptionFunc, null)
{
}
protected SessionEventArgsBase(int bufferSize, ProxyEndPoint endPoint, ExceptionHandler exceptionFunc, Request request)
{ {
this.BufferSize = bufferSize; CustomUpStreamProxyUsed = null;
this.ExceptionFunc = exceptionFunc;
ProxyClient = new ProxyClient();
WebSession = new HttpWebClient(bufferSize, request);
LocalEndPoint = endPoint;
WebSession.ProcessId = new Lazy<int>(() => DataSent = null;
{ DataReceived = null;
if (RunTime.IsWindows) Exception = null;
{
var remoteEndPoint = (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint;
//If client is localhost get the process id WebSession.FinishSession();
if (NetworkHelper.IsLocalIpAddress(remoteEndPoint.Address))
{
return NetworkHelper.GetProcessIdFromPort(remoteEndPoint.Port, endPoint.IpV6Enabled);
} }
//can't access process Id of remote request from remote machine public event EventHandler<DataEventArgs> DataSent;
return -1;
}
throw new PlatformNotSupportedException(); public event EventHandler<DataEventArgs> DataReceived;
});
}
internal void OnDataSent(byte[] buffer, int offset, int count) internal void OnDataSent(byte[] buffer, int offset, int count)
{ {
...@@ -135,17 +148,11 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -135,17 +148,11 @@ namespace Titanium.Web.Proxy.EventArguments
} }
/// <summary> /// <summary>
/// implement any cleanup here /// Terminates the session abruptly by terminating client/server connections
/// </summary> /// </summary>
public virtual void Dispose() public void TerminateSession()
{ {
CustomUpStreamProxyUsed = null; CancellationTokenSource.Cancel();
DataSent = null;
DataReceived = null;
Exception = null;
WebSession.FinishSession();
} }
} }
} }
using System; namespace Titanium.Web.Proxy.EventArguments
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.EventArguments
{ {
internal enum TransformationMode internal enum TransformationMode
{ {
...@@ -18,6 +12,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -18,6 +12,6 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary> /// <summary>
/// Uncompress the body (this also removes the chunked encoding if exists) /// Uncompress the body (this also removes the chunked encoding if exists)
/// </summary> /// </summary>
Uncompress, Uncompress
} }
} }
using System; using System;
using System.Threading;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
...@@ -6,15 +7,26 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -6,15 +7,26 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
public class TunnelConnectSessionEventArgs : SessionEventArgsBase public class TunnelConnectSessionEventArgs : SessionEventArgsBase
{ {
public bool DecryptSsl { get; set; } = true; private bool? isHttpsConnect;
public bool BlockConnect { get; set; } internal TunnelConnectSessionEventArgs(int bufferSize, ProxyEndPoint endPoint, ConnectRequest connectRequest,
CancellationTokenSource cancellationTokenSource, ExceptionHandler exceptionFunc)
: base(bufferSize, endPoint, cancellationTokenSource, connectRequest, exceptionFunc)
{
WebSession.ConnectRequest = connectRequest;
}
public bool DecryptSsl { get; set; } = true;
public bool IsHttpsConnect { get; internal set; } /// <summary>
/// Denies the connect request with a Forbidden status
/// </summary>
public bool DenyConnect { get; set; }
internal TunnelConnectSessionEventArgs(int bufferSize, ProxyEndPoint endPoint, ConnectRequest connectRequest, ExceptionHandler exceptionFunc) public bool IsHttpsConnect
: base(bufferSize, endPoint, exceptionFunc, connectRequest)
{ {
get => isHttpsConnect ?? throw new Exception("The value of this property is known in the BeforeTunnectConnectResponse event");
internal set => isHttpsConnect = value;
} }
} }
} }
...@@ -14,10 +14,11 @@ namespace Titanium.Web.Proxy.Exceptions ...@@ -14,10 +14,11 @@ namespace Titanium.Web.Proxy.Exceptions
/// Instantiate new instance /// Instantiate new instance
/// </summary> /// </summary>
/// <param name="message">Exception message</param> /// <param name="message">Exception message</param>
/// <param name="session">The <see cref="SessionEventArgs"/> instance containing the event data.</param> /// <param name="session">The <see cref="SessionEventArgs" /> instance containing the event data.</param>
/// <param name="innerException">Inner exception associated to upstream proxy authorization</param> /// <param name="innerException">Inner exception associated to upstream proxy authorization</param>
/// <param name="headers">Http's headers associated</param> /// <param name="headers">Http's headers associated</param>
internal ProxyAuthorizationException(string message, SessionEventArgsBase session, Exception innerException, IEnumerable<HttpHeader> headers) : base(message, innerException) internal ProxyAuthorizationException(string message, SessionEventArgsBase session, Exception innerException,
IEnumerable<HttpHeader> headers) : base(message, innerException)
{ {
Session = session; Session = session;
Headers = headers; Headers = headers;
......
...@@ -13,8 +13,9 @@ namespace Titanium.Web.Proxy.Exceptions ...@@ -13,8 +13,9 @@ namespace Titanium.Web.Proxy.Exceptions
/// </summary> /// </summary>
/// <param name="message">Message for this exception</param> /// <param name="message">Message for this exception</param>
/// <param name="innerException">Associated inner exception</param> /// <param name="innerException">Associated inner exception</param>
/// <param name="sessionEventArgs">Instance of <see cref="EventArguments.SessionEventArgs"/> associated to the exception</param> /// <param name="sessionEventArgs">Instance of <see cref="EventArguments.SessionEventArgs" /> associated to the exception</param>
internal ProxyHttpException(string message, Exception innerException, SessionEventArgs sessionEventArgs) : base(message, innerException) internal ProxyHttpException(string message, Exception innerException, SessionEventArgs sessionEventArgs) : base(
message, innerException)
{ {
SessionEventArgs = sessionEventArgs; SessionEventArgs = sessionEventArgs;
} }
......
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended;
using StreamExtended.Helpers;
using StreamExtended.Network;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy
{
partial class ProxyServer
{
/// <summary>
/// This is called when client is aware of proxy
/// So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
/// </summary>
/// <param name="endPoint"></param>
/// <param name="tcpClient"></param>
/// <returns></returns>
private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient)
{
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
try
{
string connectHostname = null;
TunnelConnectSessionEventArgs connectArgs = null;
//Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request)
if (await HttpHelper.IsConnectMethod(clientStream) == 1)
{
//read the first line HTTP command
string httpCmd = await clientStreamReader.ReadLineAsync(cancellationToken);
if (string.IsNullOrEmpty(httpCmd))
{
return;
}
Request.ParseRequestLine(httpCmd, out string _, out string httpUrl, out var version);
var httpRemoteUri = new Uri("http://" + httpUrl);
connectHostname = httpRemoteUri.Host;
var connectRequest = new ConnectRequest
{
RequestUri = httpRemoteUri,
OriginalUrl = httpUrl,
HttpVersion = version
};
await HeaderParser.ReadHeaders(clientStreamReader, connectRequest.Headers, cancellationToken);
connectArgs = new TunnelConnectSessionEventArgs(BufferSize, endPoint, connectRequest,
cancellationTokenSource, ExceptionFunc);
connectArgs.ProxyClient.TcpClient = tcpClient;
connectArgs.ProxyClient.ClientStream = clientStream;
await endPoint.InvokeBeforeTunnelConnectRequest(this, connectArgs, ExceptionFunc);
//filter out excluded host names
bool decryptSsl = endPoint.DecryptSsl && connectArgs.DecryptSsl;
if (connectArgs.DenyConnect)
{
if (connectArgs.WebSession.Response.StatusCode == 0)
{
connectArgs.WebSession.Response = new Response
{
HttpVersion = HttpHeader.Version11,
StatusCode = (int)HttpStatusCode.Forbidden,
StatusDescription = "Forbidden"
};
}
//send the response
await clientStreamWriter.WriteResponseAsync(connectArgs.WebSession.Response, cancellationToken: cancellationToken);
return;
}
if (await CheckAuthorization(connectArgs) == false)
{
await endPoint.InvokeBeforeTunnectConnectResponse(this, connectArgs, ExceptionFunc);
//send the response
await clientStreamWriter.WriteResponseAsync(connectArgs.WebSession.Response, cancellationToken: cancellationToken);
return;
}
//write back successfull CONNECT response
var response = ConnectResponse.CreateSuccessfullConnectResponse(version);
// Set ContentLength explicitly to properly handle HTTP 1.0
response.ContentLength = 0;
response.Headers.FixProxyHeaders();
connectArgs.WebSession.Response = response;
await clientStreamWriter.WriteResponseAsync(response, cancellationToken: cancellationToken);
var clientHelloInfo = await SslTools.PeekClientHello(clientStream, cancellationToken);
bool isClientHello = clientHelloInfo != null;
if (isClientHello)
{
connectRequest.ClientHelloInfo = clientHelloInfo;
}
await endPoint.InvokeBeforeTunnectConnectResponse(this, connectArgs, ExceptionFunc, isClientHello);
if (decryptSsl && isClientHello)
{
connectRequest.RequestUri = new Uri("https://" + httpUrl);
bool http2Supproted = false;
var alpn = clientHelloInfo.GetAlpn();
if (alpn != null && alpn.Contains(SslApplicationProtocol.Http2))
{
// test server HTTP/2 support
// todo: this is a hack, because Titanium does not support HTTP protocol changing currently
using (var connection = await GetServerConnection(connectArgs, true, cancellationToken))
{
http2Supproted = connection.IsHttp2Supported;
}
}
SslStream sslStream = null;
try
{
sslStream = new SslStream(clientStream);
string certName = HttpHelper.GetWildCardDomainName(connectHostname);
var certificate = endPoint.GenericCertificate ??
await CertificateManager.CreateCertificateAsync(certName);
//Successfully managed to authenticate the client using the fake certificate
var options = new SslServerAuthenticationOptions();
if (http2Supproted)
{
options.ApplicationProtocols = clientHelloInfo.GetAlpn();
if (options.ApplicationProtocols == null || options.ApplicationProtocols.Count == 0)
{
options.ApplicationProtocols = SslExtensions.Http11ProtocolAsList;
}
}
options.ServerCertificate = certificate;
options.ClientCertificateRequired = false;
options.EnabledSslProtocols = SupportedSslProtocols;
options.CertificateRevocationCheckMode = X509RevocationMode.NoCheck;
await sslStream.AuthenticateAsServerAsync(options, cancellationToken);
//HTTPS server created - we can now decrypt the client's traffic
clientStream = new CustomBufferedStream(sslStream, BufferSize);
clientStreamReader.Dispose();
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
}
catch (Exception e)
{
ExceptionFunc(new Exception(
$"Could'nt authenticate client '{connectHostname}' with fake certificate.", e));
sslStream?.Dispose();
return;
}
if (await HttpHelper.IsConnectMethod(clientStream) == -1)
{
decryptSsl = false;
}
}
if (cancellationTokenSource.IsCancellationRequested)
{
throw new Exception("Session was terminated by user.");
}
//Hostname is excluded or it is not an HTTPS connect
if (!decryptSsl || !isClientHello)
{
//create new connection
using (var connection = await GetServerConnection(connectArgs, true, cancellationToken))
{
if (isClientHello)
{
int available = clientStream.Available;
if (available > 0)
{
//send the buffered data
var data = BufferPool.GetBuffer(BufferSize);
try
{
// clientStream.Available sbould be at most BufferSize because it is using the same buffer size
await clientStream.ReadAsync(data, 0, available, cancellationToken);
await connection.StreamWriter.WriteAsync(data, 0, available, true, cancellationToken);
}
finally
{
BufferPool.ReturnBuffer(data);
}
}
var serverHelloInfo = await SslTools.PeekServerHello(connection.Stream, cancellationToken);
((ConnectResponse)connectArgs.WebSession.Response).ServerHelloInfo = serverHelloInfo;
}
await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize,
(buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); },
(buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); },
connectArgs.CancellationTokenSource, ExceptionFunc);
}
return;
}
}
if (connectArgs != null && await HttpHelper.IsPriMethod(clientStream) == 1)
{
// todo
string httpCmd = await clientStreamReader.ReadLineAsync(cancellationToken);
if (httpCmd == "PRI * HTTP/2.0")
{
// HTTP/2 Connection Preface
string line = await clientStreamReader.ReadLineAsync(cancellationToken);
if (line != string.Empty) throw new Exception($"HTTP/2 Protocol violation. Empty string expected, '{line}' received");
line = await clientStreamReader.ReadLineAsync(cancellationToken);
if (line != "SM") throw new Exception($"HTTP/2 Protocol violation. 'SM' expected, '{line}' received");
line = await clientStreamReader.ReadLineAsync(cancellationToken);
if (line != string.Empty) throw new Exception($"HTTP/2 Protocol violation. Empty string expected, '{line}' received");
//create new connection
using (var connection = await GetServerConnection(connectArgs, true, cancellationToken))
{
await connection.StreamWriter.WriteLineAsync("PRI * HTTP/2.0", cancellationToken);
await connection.StreamWriter.WriteLineAsync(cancellationToken);
await connection.StreamWriter.WriteLineAsync("SM", cancellationToken);
await connection.StreamWriter.WriteLineAsync(cancellationToken);
await TcpHelper.SendHttp2(clientStream, connection.Stream, BufferSize,
(buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); },
(buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); },
connectArgs.CancellationTokenSource, ExceptionFunc);
}
}
}
//Now create the request
await HandleHttpSessionRequest(endPoint, tcpClient, clientStream, clientStreamReader,
clientStreamWriter, cancellationTokenSource, connectHostname, connectArgs?.WebSession.ConnectRequest);
}
catch (ProxyHttpException e)
{
ExceptionFunc(e);
}
catch (IOException e)
{
ExceptionFunc(new Exception("Connection was aborted", e));
}
catch (SocketException e)
{
ExceptionFunc(new Exception("Could not connect", e));
}
catch (Exception e)
{
ExceptionFunc(new Exception("Error occured in whilst handling the client", e));
}
finally
{
clientStreamReader.Dispose();
clientStream.Dispose();
if (!cancellationTokenSource.IsCancellationRequested)
{
cancellationTokenSource.Cancel();
}
}
}
}
}
using System;
namespace Titanium.Web.Proxy.Extensions
{
/// <summary>
/// Extension methods for Byte Arrays.
/// </summary>
internal static class ByteArrayExtensions
{
/// <summary>
/// Get the sub array from byte of data
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="data"></param>
/// <param name="index"></param>
/// <param name="length"></param>
/// <returns></returns>
internal static T[] SubArray<T>(this T[] data, int index, int length)
{
var result = new T[length];
Array.Copy(data, index, result, 0, length);
return result;
}
}
}
...@@ -6,8 +6,8 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -6,8 +6,8 @@ namespace Titanium.Web.Proxy.Extensions
{ {
internal static class FuncExtensions internal static class FuncExtensions
{ {
public static async Task InvokeAsync<T>(this AsyncEventHandler<T> callback, object sender, T args,
public static async Task InvokeAsync<T>(this AsyncEventHandler<T> callback, object sender, T args, ExceptionHandler exceptionFunc) ExceptionHandler exceptionFunc)
{ {
var invocationList = callback.GetInvocationList(); var invocationList = callback.GetInvocationList();
...@@ -17,7 +17,8 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -17,7 +17,8 @@ namespace Titanium.Web.Proxy.Extensions
} }
} }
private static async Task InternalInvokeAsync<T>(AsyncEventHandler<T> callback, object sender, T args, ExceptionHandler exceptionFunc) private static async Task InternalInvokeAsync<T>(AsyncEventHandler<T> callback, object sender, T args,
ExceptionHandler exceptionFunc)
{ {
try try
{ {
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended; using StreamExtended;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
static class SslExtensions internal static class SslExtensions
{ {
public static readonly List<SslApplicationProtocol> Http11ProtocolAsList = new List<SslApplicationProtocol> { SslApplicationProtocol.Http11 };
public static string GetServerName(this ClientHelloInfo clientHelloInfo) public static string GetServerName(this ClientHelloInfo clientHelloInfo)
{ {
if (clientHelloInfo.Extensions != null && clientHelloInfo.Extensions.TryGetValue("server_name", out var serverNameExtension)) if (clientHelloInfo.Extensions != null &&
clientHelloInfo.Extensions.TryGetValue("server_name", out var serverNameExtension))
{ {
return serverNameExtension.Data; return serverNameExtension.Data;
} }
return null; return null;
} }
#if NETCOREAPP2_1
public static List<SslApplicationProtocol> GetAlpn(this ClientHelloInfo clientHelloInfo)
{
if (clientHelloInfo.Extensions != null && clientHelloInfo.Extensions.TryGetValue("ALPN", out var alpnExtension))
{
var alpn = alpnExtension.Data.Split(',');
if (alpn.Length != 0)
{
var result = new List<SslApplicationProtocol>(alpn.Length);
foreach (string p in alpn)
{
string protocol = p.Trim();
if (protocol.Equals("http/1.1"))
{
result.Add(SslApplicationProtocol.Http11);
}
else if (protocol.Equals("h2"))
{
result.Add(SslApplicationProtocol.Http2);
}
}
return result;
}
}
return null;
}
#else
public static List<SslApplicationProtocol> GetAlpn(this ClientHelloInfo clientHelloInfo)
{
return Http11ProtocolAsList;
}
public static Task AuthenticateAsClientAsync(this SslStream sslStream, SslClientAuthenticationOptions option, CancellationToken token)
{
return sslStream.AuthenticateAsClientAsync(option.TargetHost, option.ClientCertificates, option.EnabledSslProtocols, option.CertificateRevocationCheckMode != X509RevocationMode.NoCheck);
}
public static Task AuthenticateAsServerAsync(this SslStream sslStream, SslServerAuthenticationOptions options, CancellationToken token)
{
return sslStream.AuthenticateAsServerAsync(options.ServerCertificate, options.ClientCertificateRequired, options.EnabledSslProtocols, options.CertificateRevocationCheckMode != X509RevocationMode.NoCheck);
}
#endif
}
#if !NETCOREAPP2_1
public enum SslApplicationProtocol
{
Http11, Http2
}
public class SslClientAuthenticationOptions
{
public bool AllowRenegotiation { get; set; }
public string TargetHost { get; set; }
public X509CertificateCollection ClientCertificates { get; set; }
public LocalCertificateSelectionCallback LocalCertificateSelectionCallback { get; set; }
public SslProtocols EnabledSslProtocols { get; set; }
public X509RevocationMode CertificateRevocationCheckMode { get; set; }
public List<SslApplicationProtocol> ApplicationProtocols { get; set; }
public RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; }
public EncryptionPolicy EncryptionPolicy { get; set; }
}
public class SslServerAuthenticationOptions
{
public bool AllowRenegotiation { get; set; }
public X509Certificate ServerCertificate { get; set; }
public bool ClientCertificateRequired { get; set; }
public SslProtocols EnabledSslProtocols { get; set; }
public X509RevocationMode CertificateRevocationCheckMode { get; set; }
public List<SslApplicationProtocol> ApplicationProtocols { get; set; }
public RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; }
public EncryptionPolicy EncryptionPolicy { get; set; }
} }
#endif
} }
...@@ -18,7 +18,8 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -18,7 +18,8 @@ namespace Titanium.Web.Proxy.Extensions
/// <param name="output"></param> /// <param name="output"></param>
/// <param name="onCopy"></param> /// <param name="onCopy"></param>
/// <param name="bufferSize"></param> /// <param name="bufferSize"></param>
internal static Task CopyToAsync(this Stream input, Stream output, Action<byte[], int, int> onCopy, int bufferSize) internal static Task CopyToAsync(this Stream input, Stream output, Action<byte[], int, int> onCopy,
int bufferSize)
{ {
return CopyToAsync(input, output, onCopy, bufferSize, CancellationToken.None); return CopyToAsync(input, output, onCopy, bufferSize, CancellationToken.None);
} }
...@@ -31,16 +32,18 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -31,16 +32,18 @@ namespace Titanium.Web.Proxy.Extensions
/// <param name="onCopy"></param> /// <param name="onCopy"></param>
/// <param name="bufferSize"></param> /// <param name="bufferSize"></param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
internal static async Task CopyToAsync(this Stream input, Stream output, Action<byte[], int, int> onCopy, int bufferSize, CancellationToken cancellationToken) internal static async Task CopyToAsync(this Stream input, Stream output, Action<byte[], int, int> onCopy,
int bufferSize, CancellationToken cancellationToken)
{ {
byte[] buffer = BufferPool.GetBuffer(bufferSize); var buffer = BufferPool.GetBuffer(bufferSize);
try try
{ {
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
// cancellation is not working on Socket ReadAsync // cancellation is not working on Socket ReadAsync
// https://github.com/dotnet/corefx/issues/15033 // https://github.com/dotnet/corefx/issues/15033
int num = await input.ReadAsync(buffer, 0, buffer.Length, CancellationToken.None).WithCancellation(cancellationToken); int num = await input.ReadAsync(buffer, 0, buffer.Length, CancellationToken.None)
.WithCancellation(cancellationToken);
int bytesRead; int bytesRead;
if ((bytesRead = num) != 0 && !cancellationToken.IsCancellationRequested) if ((bytesRead = num) != 0 && !cancellationToken.IsCancellationRequested)
{ {
...@@ -66,7 +69,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -66,7 +69,7 @@ namespace Titanium.Web.Proxy.Extensions
{ {
if (task != await Task.WhenAny(task, tcs.Task)) if (task != await Task.WhenAny(task, tcs.Task))
{ {
return default(T); return default;
} }
} }
......
using System; using System;
using System.Net.Sockets; using System.Net.Sockets;
using System.Reflection; using System.Reflection;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
...@@ -17,31 +16,12 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -17,31 +16,12 @@ namespace Titanium.Web.Proxy.Extensions
var method = property.GetMethod; var method = property.GetMethod;
if (method != null && method.ReturnType == typeof(bool)) if (method != null && method.ReturnType == typeof(bool))
{ {
socketCleanedUpGetter = (Func<Socket, bool>)Delegate.CreateDelegate(typeof(Func<Socket, bool>), method); socketCleanedUpGetter =
(Func<Socket, bool>)Delegate.CreateDelegate(typeof(Func<Socket, bool>), method);
} }
} }
} }
/// <summary>
/// Gets the local port from a native TCP row object.
/// </summary>
/// <param name="tcpRow">The TCP row.</param>
/// <returns>The local port</returns>
internal static int GetLocalPort(this NativeMethods.TcpRow tcpRow)
{
return (tcpRow.localPort1 << 8) + tcpRow.localPort2 + (tcpRow.localPort3 << 24) + (tcpRow.localPort4 << 16);
}
/// <summary>
/// Gets the remote port from a native TCP row object.
/// </summary>
/// <param name="tcpRow">The TCP row.</param>
/// <returns>The remote port</returns>
internal static int GetRemotePort(this NativeMethods.TcpRow tcpRow)
{
return (tcpRow.remotePort1 << 8) + tcpRow.remotePort2 + (tcpRow.remotePort3 << 24) + (tcpRow.remotePort4 << 16);
}
internal static void CloseSocket(this TcpClient tcpClient) internal static void CloseSocket(this TcpClient tcpClient)
{ {
if (tcpClient == null) if (tcpClient == null)
...@@ -64,6 +44,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -64,6 +44,7 @@ namespace Titanium.Web.Proxy.Extensions
} }
catch catch
{ {
// ignored
} }
} }
} }
......
using StreamExtended.Network; using System;
using System;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended.Network;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
...@@ -37,7 +37,6 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -37,7 +37,6 @@ namespace Titanium.Web.Proxy.Helpers
string value = split[1]; string value = split[1];
if (value.Equals("x-user-defined", StringComparison.OrdinalIgnoreCase)) if (value.Equals("x-user-defined", StringComparison.OrdinalIgnoreCase))
{ {
//todo: what is this?
continue; continue;
} }
...@@ -103,7 +102,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -103,7 +102,7 @@ namespace Titanium.Web.Proxy.Helpers
int idx = hostname.IndexOf(ProxyConstants.DotSplit); int idx = hostname.IndexOf(ProxyConstants.DotSplit);
//issue #352 //issue #352
if(hostname.Substring(0, idx).Contains("-")) if (hostname.Substring(0, idx).Contains("-"))
{ {
return hostname; return hostname;
} }
...@@ -121,9 +120,32 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -121,9 +120,32 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary> /// </summary>
/// <param name="clientStream">The client stream.</param> /// <param name="clientStream">The client stream.</param>
/// <returns>1: when CONNECT, 0: when valid HTTP method, -1: otherwise</returns> /// <returns>1: when CONNECT, 0: when valid HTTP method, -1: otherwise</returns>
internal static async Task<int> IsConnectMethod(CustomBufferedStream clientStream) internal static Task<int> IsConnectMethod(CustomBufferedStream clientStream)
{ {
bool isConnect = true; return StartsWith(clientStream, "CONNECT");
}
/// <summary>
/// Determines whether is pri method (HTTP/2).
/// </summary>
/// <param name="clientStream">The client stream.</param>
/// <returns>1: when PRI, 0: when valid HTTP method, -1: otherwise</returns>
internal static Task<int> IsPriMethod(CustomBufferedStream clientStream)
{
return StartsWith(clientStream, "PRI");
}
/// <summary>
/// Determines whether the stream starts with the given string.
/// </summary>
/// <param name="clientStream">The client stream.</param>
/// <param name="expectedStart">The expected start.</param>
/// <returns>
/// 1: when starts with the given string, 0: when valid HTTP method, -1: otherwise
/// </returns>
private static async Task<int> StartsWith(CustomBufferedStream clientStream, string expectedStart)
{
bool isExpected = true;
int legthToCheck = 10; int legthToCheck = 10;
for (int i = 0; i < legthToCheck; i++) for (int i = 0; i < legthToCheck; i++)
{ {
...@@ -135,25 +157,23 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -135,25 +157,23 @@ namespace Titanium.Web.Proxy.Helpers
if (b == ' ' && i > 2) if (b == ' ' && i > 2)
{ {
// at least 3 letters and a space return isExpected ? 1 : 0;
return isConnect ? 1 : 0;
} }
char ch = (char)b; char ch = (char)b;
if (!char.IsLetter(ch)) if (!char.IsLetter(ch))
{ {
// non letter or too short
return -1; return -1;
} }
if (i > 6 || ch != "CONNECT"[i]) if (i >= expectedStart.Length || ch != expectedStart[i])
{ {
isConnect = false; isExpected = false;
} }
} }
// only letters // only letters
return isConnect ? 1 : 0; return isExpected ? 1 : 0;
} }
} }
} }
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
sealed class HttpRequestWriter : HttpWriter internal sealed class HttpRequestWriter : HttpWriter
{ {
public HttpRequestWriter(Stream stream, int bufferSize) : base(stream, bufferSize) public HttpRequestWriter(Stream stream, int bufferSize) : base(stream, bufferSize)
{ {
} }
/// <summary>
/// Writes the request.
/// </summary>
/// <param name="request"></param>
/// <param name="flush"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task WriteRequestAsync(Request request, bool flush = true,
CancellationToken cancellationToken = default)
{
await WriteLineAsync(Request.CreateRequestLine(request.Method, request.OriginalUrl, request.HttpVersion),
cancellationToken);
await WriteAsync(request, flush, cancellationToken);
}
} }
} }
using System; using System;
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
sealed class HttpResponseWriter : HttpWriter internal sealed class HttpResponseWriter : HttpWriter
{ {
public HttpResponseWriter(Stream stream, int bufferSize) : base(stream, bufferSize) public HttpResponseWriter(Stream stream, int bufferSize) : base(stream, bufferSize)
{ {
...@@ -16,11 +17,14 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -16,11 +17,14 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary> /// </summary>
/// <param name="response"></param> /// <param name="response"></param>
/// <param name="flush"></param> /// <param name="flush"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
public async Task WriteResponseAsync(Response response, bool flush = true) public async Task WriteResponseAsync(Response response, bool flush = true,
CancellationToken cancellationToken = default)
{ {
await WriteResponseStatusAsync(response.HttpVersion, response.StatusCode, response.StatusDescription); await WriteResponseStatusAsync(response.HttpVersion, response.StatusCode, response.StatusDescription,
await WriteAsync(response, flush); cancellationToken);
await WriteAsync(response, flush, cancellationToken);
} }
/// <summary> /// <summary>
...@@ -29,10 +33,11 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -29,10 +33,11 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="version"></param> /// <param name="version"></param>
/// <param name="code"></param> /// <param name="code"></param>
/// <param name="description"></param> /// <param name="description"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
public Task WriteResponseStatusAsync(Version version, int code, string description) public Task WriteResponseStatusAsync(Version version, int code, string description, CancellationToken cancellationToken)
{ {
return WriteLineAsync(Response.CreateResponseLine(version, code, description)); return WriteLineAsync(Response.CreateResponseLine(version, code, description), cancellationToken);
} }
} }
} }
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended.Helpers; using StreamExtended.Helpers;
using StreamExtended.Network; using StreamExtended.Network;
using Titanium.Web.Proxy.Compression;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
...@@ -13,14 +13,12 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -13,14 +13,12 @@ namespace Titanium.Web.Proxy.Helpers
{ {
internal class HttpWriter : CustomBinaryWriter internal class HttpWriter : CustomBinaryWriter
{ {
public int BufferSize { get; }
private readonly char[] charBuffer;
private static readonly byte[] newLine = ProxyConstants.NewLine; private static readonly byte[] newLine = ProxyConstants.NewLine;
private static readonly Encoder encoder = Encoding.ASCII.GetEncoder(); private static readonly Encoder encoder = Encoding.ASCII.GetEncoder();
private readonly char[] charBuffer;
internal HttpWriter(Stream stream, int bufferSize) : base(stream) internal HttpWriter(Stream stream, int bufferSize) : base(stream)
{ {
BufferSize = bufferSize; BufferSize = bufferSize;
...@@ -29,17 +27,19 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -29,17 +27,19 @@ namespace Titanium.Web.Proxy.Helpers
charBuffer = new char[BufferSize - 1]; charBuffer = new char[BufferSize - 1];
} }
public Task WriteLineAsync() public int BufferSize { get; }
public Task WriteLineAsync(CancellationToken cancellationToken = default)
{ {
return WriteAsync(newLine); return WriteAsync(newLine, cancellationToken: cancellationToken);
} }
public Task WriteAsync(string value) public Task WriteAsync(string value, CancellationToken cancellationToken = default)
{ {
return WriteAsyncInternal(value, false); return WriteAsyncInternal(value, false, cancellationToken);
} }
private Task WriteAsyncInternal(string value, bool addNewLine) private Task WriteAsyncInternal(string value, bool addNewLine, CancellationToken cancellationToken)
{ {
int newLineChars = addNewLine ? newLine.Length : 0; int newLineChars = addNewLine ? newLine.Length : 0;
int charCount = value.Length; int charCount = value.Length;
...@@ -57,7 +57,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -57,7 +57,7 @@ namespace Titanium.Web.Proxy.Helpers
idx += newLineChars; idx += newLineChars;
} }
return WriteAsync(buffer, 0, idx); return WriteAsync(buffer, 0, idx, cancellationToken);
} }
finally finally
{ {
...@@ -77,13 +77,13 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -77,13 +77,13 @@ namespace Titanium.Web.Proxy.Helpers
idx += newLineChars; idx += newLineChars;
} }
return WriteAsync(buffer, 0, idx); return WriteAsync(buffer, 0, idx, cancellationToken);
} }
} }
public Task WriteLineAsync(string value) public Task WriteLineAsync(string value, CancellationToken cancellationToken = default)
{ {
return WriteAsyncInternal(value, true); return WriteAsyncInternal(value, true, cancellationToken);
} }
/// <summary> /// <summary>
...@@ -91,39 +91,37 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -91,39 +91,37 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary> /// </summary>
/// <param name="headers"></param> /// <param name="headers"></param>
/// <param name="flush"></param> /// <param name="flush"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
public async Task WriteHeadersAsync(HeaderCollection headers, bool flush = true) public async Task WriteHeadersAsync(HeaderCollection headers, bool flush = true, CancellationToken cancellationToken = default)
{ {
foreach (var header in headers) foreach (var header in headers)
{ {
await header.WriteToStreamAsync(this); await header.WriteToStreamAsync(this, cancellationToken);
} }
await WriteLineAsync(); await WriteLineAsync(cancellationToken);
if (flush) if (flush)
{ {
// flush the stream await FlushAsync(cancellationToken);
await FlushAsync();
} }
} }
public async Task WriteAsync(byte[] data, bool flush = false) public async Task WriteAsync(byte[] data, bool flush = false, CancellationToken cancellationToken = default)
{ {
await WriteAsync(data, 0, data.Length); await WriteAsync(data, 0, data.Length, cancellationToken);
if (flush) if (flush)
{ {
// flush the stream await FlushAsync(cancellationToken);
await FlushAsync();
} }
} }
public async Task WriteAsync(byte[] data, int offset, int count, bool flush) public async Task WriteAsync(byte[] data, int offset, int count, bool flush, CancellationToken cancellationToken = default)
{ {
await WriteAsync(data, offset, count); await WriteAsync(data, offset, count, cancellationToken);
if (flush) if (flush)
{ {
// flush the stream await FlushAsync(cancellationToken);
await FlushAsync();
} }
} }
...@@ -132,15 +130,16 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -132,15 +130,16 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary> /// </summary>
/// <param name="data"></param> /// <param name="data"></param>
/// <param name="isChunked"></param> /// <param name="isChunked"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
internal Task WriteBodyAsync(byte[] data, bool isChunked) internal Task WriteBodyAsync(byte[] data, bool isChunked, CancellationToken cancellationToken)
{ {
if (isChunked) if (isChunked)
{ {
return WriteBodyChunkedAsync(data); return WriteBodyChunkedAsync(data, cancellationToken);
} }
return WriteAsync(data); return WriteAsync(data, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
...@@ -151,15 +150,15 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -151,15 +150,15 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="isChunked"></param> /// <param name="isChunked"></param>
/// <param name="contentLength"></param> /// <param name="contentLength"></param>
/// <param name="onCopy"></param> /// <param name="onCopy"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
internal Task CopyBodyAsync(CustomBinaryReader streamReader, bool isChunked, long contentLength, Action<byte[], int, int> onCopy) internal Task CopyBodyAsync(CustomBinaryReader streamReader, bool isChunked, long contentLength,
Action<byte[], int, int> onCopy, CancellationToken cancellationToken)
{ {
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol //For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if (isChunked) if (isChunked)
{ {
//Need to revist, find any potential bugs return CopyBodyChunkedAsync(streamReader, onCopy, cancellationToken);
//send the body bytes to server in chunks
return CopyBodyChunkedAsync(streamReader, onCopy);
} }
//http 1.0 or the stream reader limits the stream //http 1.0 or the stream reader limits the stream
...@@ -169,25 +168,26 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -169,25 +168,26 @@ namespace Titanium.Web.Proxy.Helpers
} }
//If not chunked then its easy just read the amount of bytes mentioned in content length header //If not chunked then its easy just read the amount of bytes mentioned in content length header
return CopyBytesFromStream(streamReader, contentLength, onCopy); return CopyBytesFromStream(streamReader, contentLength, onCopy, cancellationToken);
} }
/// <summary> /// <summary>
/// Copies the given input bytes to output stream chunked /// Copies the given input bytes to output stream chunked
/// </summary> /// </summary>
/// <param name="data"></param> /// <param name="data"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
private async Task WriteBodyChunkedAsync(byte[] data) private async Task WriteBodyChunkedAsync(byte[] data, CancellationToken cancellationToken)
{ {
var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2")); var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2"));
await WriteAsync(chunkHead); await WriteAsync(chunkHead, cancellationToken: cancellationToken);
await WriteLineAsync(); await WriteLineAsync(cancellationToken);
await WriteAsync(data); await WriteAsync(data, cancellationToken: cancellationToken);
await WriteLineAsync(); await WriteLineAsync(cancellationToken);
await WriteLineAsync("0"); await WriteLineAsync("0", cancellationToken);
await WriteLineAsync(); await WriteLineAsync(cancellationToken);
} }
/// <summary> /// <summary>
...@@ -195,32 +195,32 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -195,32 +195,32 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary> /// </summary>
/// <param name="reader"></param> /// <param name="reader"></param>
/// <param name="onCopy"></param> /// <param name="onCopy"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
private async Task CopyBodyChunkedAsync(CustomBinaryReader reader, Action<byte[], int, int> onCopy) private async Task CopyBodyChunkedAsync(CustomBinaryReader reader, Action<byte[], int, int> onCopy, CancellationToken cancellationToken)
{ {
while (true) while (true)
{ {
string chunkHead = await reader.ReadLineAsync(); string chunkHead = await reader.ReadLineAsync(cancellationToken);
int idx = chunkHead.IndexOf(";"); int idx = chunkHead.IndexOf(";");
if (idx >= 0) if (idx >= 0)
{ {
// remove chunk extension
chunkHead = chunkHead.Substring(0, idx); chunkHead = chunkHead.Substring(0, idx);
} }
int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber); int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
await WriteLineAsync(chunkHead); await WriteLineAsync(chunkHead, cancellationToken);
if (chunkSize != 0) if (chunkSize != 0)
{ {
await CopyBytesFromStream(reader, chunkSize, onCopy); await CopyBytesFromStream(reader, chunkSize, onCopy, cancellationToken);
} }
await WriteLineAsync(); await WriteLineAsync(cancellationToken);
//chunk trail //chunk trail
await reader.ReadLineAsync(); await reader.ReadLineAsync(cancellationToken);
if (chunkSize == 0) if (chunkSize == 0)
{ {
...@@ -235,8 +235,9 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -235,8 +235,9 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="reader"></param> /// <param name="reader"></param>
/// <param name="count"></param> /// <param name="count"></param>
/// <param name="onCopy"></param> /// <param name="onCopy"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
private async Task CopyBytesFromStream(CustomBinaryReader reader, long count, Action<byte[], int, int> onCopy) private async Task CopyBytesFromStream(CustomBinaryReader reader, long count, Action<byte[], int, int> onCopy, CancellationToken cancellationToken)
{ {
var buffer = reader.Buffer; var buffer = reader.Buffer;
long remainingBytes = count; long remainingBytes = count;
...@@ -249,7 +250,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -249,7 +250,7 @@ namespace Titanium.Web.Proxy.Helpers
bytesToRead = (int)remainingBytes; bytesToRead = (int)remainingBytes;
} }
int bytesRead = await reader.ReadBytesAsync(buffer, bytesToRead); int bytesRead = await reader.ReadBytesAsync(buffer, bytesToRead, cancellationToken);
if (bytesRead == 0) if (bytesRead == 0)
{ {
break; break;
...@@ -257,7 +258,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -257,7 +258,7 @@ namespace Titanium.Web.Proxy.Helpers
remainingBytes -= bytesRead; remainingBytes -= bytesRead;
await WriteAsync(buffer, 0, bytesRead); await WriteAsync(buffer, 0, bytesRead, cancellationToken);
onCopy?.Invoke(buffer, 0, bytesRead); onCopy?.Invoke(buffer, 0, bytesRead);
} }
...@@ -268,15 +269,16 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -268,15 +269,16 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary> /// </summary>
/// <param name="requestResponse"></param> /// <param name="requestResponse"></param>
/// <param name="flush"></param> /// <param name="flush"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
protected async Task WriteAsync(RequestResponseBase requestResponse, bool flush = true) protected async Task WriteAsync(RequestResponseBase requestResponse, bool flush = true, CancellationToken cancellationToken = default)
{ {
var body = requestResponse.CompressBodyAndUpdateContentLength(); var body = requestResponse.CompressBodyAndUpdateContentLength();
await WriteHeadersAsync(requestResponse.Headers, flush); await WriteHeadersAsync(requestResponse.Headers, flush, cancellationToken);
if (body != null) if (body != null)
{ {
await WriteBodyAsync(body, requestResponse.IsChunked); await WriteBodyAsync(body, requestResponse.IsChunked, cancellationToken);
} }
} }
} }
......
...@@ -5,15 +5,16 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -5,15 +5,16 @@ namespace Titanium.Web.Proxy.Helpers
{ {
internal partial class NativeMethods internal partial class NativeMethods
{ {
// Keeps it from getting garbage collected
internal static ConsoleEventDelegate Handler;
[DllImport("wininet.dll")] [DllImport("wininet.dll")]
internal static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int dwBufferLength); internal static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer,
int dwBufferLength);
[DllImport("kernel32.dll")] [DllImport("kernel32.dll")]
internal static extern IntPtr GetConsoleWindow(); internal static extern IntPtr GetConsoleWindow();
// Keeps it from getting garbage collected
internal static ConsoleEventDelegate Handler;
[DllImport("kernel32.dll", SetLastError = true)] [DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetConsoleCtrlHandler(ConsoleEventDelegate callback, bool add); internal static extern bool SetConsoleCtrlHandler(ConsoleEventDelegate callback, bool add);
......
...@@ -9,6 +9,13 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -9,6 +9,13 @@ namespace Titanium.Web.Proxy.Helpers
internal const int AfInet = 2; internal const int AfInet = 2;
internal const int AfInet6 = 23; internal const int AfInet6 = 23;
/// <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 enum TcpTableType internal enum TcpTableType
{ {
BasicListener, BasicListener,
...@@ -19,43 +26,37 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -19,43 +26,37 @@ namespace Titanium.Web.Proxy.Helpers
OwnerPidAll, OwnerPidAll,
OwnerModuleListener, OwnerModuleListener,
OwnerModuleConnections, OwnerModuleConnections,
OwnerModuleAll, 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> /// <summary>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/> /// <see href="http://msdn2.microsoft.com/en-us/library/aa366913.aspx" />
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal struct TcpRow internal struct TcpRow
{ {
public TcpState state; public TcpState state;
public uint localAddr; public uint localAddr;
public byte localPort1; public uint localPort; // in network byte order (order of bytes - 1,0,3,2)
public byte localPort2;
public byte localPort3;
public byte localPort4;
public uint remoteAddr; public uint remoteAddr;
public byte remotePort1; public uint remotePort; // in network byte order (order of bytes - 1,0,3,2)
public byte remotePort2;
public byte remotePort3;
public byte remotePort4;
public int owningPid; public int owningPid;
} }
/// <summary> /// <summary>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/> /// <see href="https://msdn.microsoft.com/en-us/library/aa366896.aspx"/>
/// </summary> /// </summary>
[DllImport("iphlpapi.dll", SetLastError = true)] [StructLayout(LayoutKind.Sequential)]
internal static extern uint GetExtendedTcpTable(IntPtr tcpTable, ref int size, bool sort, int ipVersion, int tableClass, int reserved); internal unsafe struct Tcp6Row
{
public fixed byte localAddr[16];
public uint localScopeId;
public uint localPort; // in network byte order (order of bytes - 1,0,3,2)
public fixed byte remoteAddr[16];
public uint remoteScopeId;
public uint remotePort; // in network byte order (order of bytes - 1,0,3,2)
public TcpState state;
public int owningPid;
}
} }
} }
...@@ -6,25 +6,6 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -6,25 +6,6 @@ namespace Titanium.Web.Proxy.Helpers
{ {
internal class NetworkHelper internal class NetworkHelper
{ {
private static int FindProcessIdFromLocalPort(int port, IpVersion ipVersion)
{
var tcpRow = TcpHelper.GetTcpRowByLocalPort(ipVersion, port);
return tcpRow?.ProcessId ?? 0;
}
internal static int GetProcessIdFromPort(int port, bool ipV6Enabled)
{
int processId = FindProcessIdFromLocalPort(port, IpVersion.Ipv4);
if (processId > 0 && !ipV6Enabled)
{
return processId;
}
return FindProcessIdFromLocalPort(port, IpVersion.Ipv6);
}
/// <summary> /// <summary>
/// Adapated from below link /// Adapated from below link
/// http://stackoverflow.com/questions/11834091/how-to-check-if-localhost /// http://stackoverflow.com/questions/11834091/how-to-check-if-localhost
...@@ -33,10 +14,15 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -33,10 +14,15 @@ namespace Titanium.Web.Proxy.Helpers
/// <returns></returns> /// <returns></returns>
internal static bool IsLocalIpAddress(IPAddress address) internal static bool IsLocalIpAddress(IPAddress address)
{ {
if (IPAddress.IsLoopback(address))
{
return true;
}
// get local IP addresses // get local IP addresses
var localIPs = Dns.GetHostAddresses(Dns.GetHostName()); var localIPs = Dns.GetHostAddresses(Dns.GetHostName());
// test if any host IP equals to any local IP or to localhost // test if any host IP equals to any local IP or to localhost
return IPAddress.IsLoopback(address) || localIPs.Contains(address); return localIPs.Contains(address);
} }
internal static bool IsLocalIpAddress(string hostName) internal static bool IsLocalIpAddress(string hostName)
...@@ -55,7 +41,9 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -55,7 +41,9 @@ namespace Titanium.Web.Proxy.Helpers
localhost = Dns.GetHostEntry(Dns.GetHostName()); localhost = Dns.GetHostEntry(Dns.GetHostName());
if (IPAddress.TryParse(hostName, out var ipAddress)) if (IPAddress.TryParse(hostName, out var ipAddress))
{
isLocalhost = localhost.AddressList.Any(x => x.Equals(ipAddress)); isLocalhost = localhost.AddressList.Any(x => x.Equals(ipAddress));
}
if (!isLocalhost) if (!isLocalhost)
{ {
......
...@@ -8,25 +8,8 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -8,25 +8,8 @@ namespace Titanium.Web.Proxy.Helpers
{ {
internal class ProxyInfo internal class ProxyInfo
{ {
public bool? AutoDetect { get; } public ProxyInfo(bool? autoDetect, string autoConfigUrl, int? proxyEnable, string proxyServer,
string proxyOverride)
public string AutoConfigUrl { get; }
public int? ProxyEnable { get; }
public string ProxyServer { get; }
public string ProxyOverride { get; }
public bool BypassLoopback { get; }
public bool BypassOnLocal { get; }
public Dictionary<ProxyProtocolType, HttpSystemProxyValue> Proxies { get; }
public string[] BypassList { get; }
public ProxyInfo(bool? autoDetect, string autoConfigUrl, int? proxyEnable, string proxyServer, string proxyOverride)
{ {
AutoDetect = autoDetect; AutoDetect = autoDetect;
AutoConfigUrl = autoConfigUrl; AutoConfigUrl = autoConfigUrl;
...@@ -68,10 +51,29 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -68,10 +51,29 @@ namespace Titanium.Web.Proxy.Helpers
} }
} }
public bool? AutoDetect { get; }
public string AutoConfigUrl { get; }
public int? ProxyEnable { get; }
public string ProxyServer { get; }
public string ProxyOverride { get; }
public bool BypassLoopback { get; }
public bool BypassOnLocal { get; }
public Dictionary<ProxyProtocolType, HttpSystemProxyValue> Proxies { get; }
public string[] BypassList { get; }
private static string BypassStringEscape(string rawString) private static string BypassStringEscape(string rawString)
{ {
var match = var match =
new Regex("^(?<scheme>.*://)?(?<host>[^:]*)(?<port>:[0-9]{1,5})?$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant).Match(rawString); 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;
...@@ -92,10 +94,14 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -92,10 +94,14 @@ namespace Titanium.Web.Proxy.Helpers
string str2 = ConvertRegexReservedChars(rawString1); string str2 = ConvertRegexReservedChars(rawString1);
string str3 = ConvertRegexReservedChars(empty2); string str3 = ConvertRegexReservedChars(empty2);
if (str1 == string.Empty) if (str1 == string.Empty)
{
str1 = "(?:.*://)?"; str1 = "(?:.*://)?";
}
if (str3 == string.Empty) if (str3 == string.Empty)
{
str3 = "(?::[0-9]{1,5})?"; str3 = "(?::[0-9]{1,5})?";
}
return "^" + str1 + str2 + str3 + "$"; return "^" + str1 + str2 + str3 + "$";
} }
...@@ -103,15 +109,21 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -103,15 +109,21 @@ namespace Titanium.Web.Proxy.Helpers
private static string ConvertRegexReservedChars(string rawString) private static string ConvertRegexReservedChars(string rawString)
{ {
if (rawString.Length == 0) if (rawString.Length == 0)
{
return rawString; return rawString;
}
var stringBuilder = new StringBuilder(); var stringBuilder = new StringBuilder();
foreach (char ch in rawString) foreach (char ch in rawString)
{ {
if ("#$()+.?[\\^{|".IndexOf(ch) != -1) if ("#$()+.?[\\^{|".IndexOf(ch) != -1)
{
stringBuilder.Append('\\'); stringBuilder.Append('\\');
}
else if (ch == 42) else if (ch == 42)
{
stringBuilder.Append('.'); stringBuilder.Append('.');
}
stringBuilder.Append(ch); stringBuilder.Append(ch);
} }
...@@ -131,7 +143,8 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -131,7 +143,8 @@ namespace Titanium.Web.Proxy.Helpers
{ {
protocolType = ProxyProtocolType.Http; protocolType = ProxyProtocolType.Http;
} }
else if (protocolTypeStr.Equals(Proxy.ProxyServer.UriSchemeHttps, StringComparison.InvariantCultureIgnoreCase)) else if (protocolTypeStr.Equals(Proxy.ProxyServer.UriSchemeHttps,
StringComparison.InvariantCultureIgnoreCase))
{ {
protocolType = ProxyProtocolType.Https; protocolType = ProxyProtocolType.Https;
} }
...@@ -149,7 +162,9 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -149,7 +162,9 @@ namespace Titanium.Web.Proxy.Helpers
var result = new List<HttpSystemProxyValue>(); var result = new List<HttpSystemProxyValue>();
if (string.IsNullOrWhiteSpace(proxyServerValues)) if (string.IsNullOrWhiteSpace(proxyServerValues))
{
return result; return result;
}
var proxyValues = proxyServerValues.Split(';'); var proxyValues = proxyServerValues.Split(';');
...@@ -161,8 +176,10 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -161,8 +176,10 @@ namespace Titanium.Web.Proxy.Helpers
{ {
var parsedValue = ParseProxyValue(proxyServerValues); var parsedValue = ParseProxyValue(proxyServerValues);
if (parsedValue != null) if (parsedValue != null)
{
result.Add(parsedValue); result.Add(parsedValue);
} }
}
return result; return result;
} }
...@@ -189,7 +206,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -189,7 +206,7 @@ namespace Titanium.Web.Proxy.Helpers
{ {
HostName = endPointParts[0], HostName = endPointParts[0],
Port = int.Parse(endPointParts[1]), Port = int.Parse(endPointParts[1]),
ProtocolType = protocolType.Value, ProtocolType = protocolType.Value
}; };
} }
} }
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Helpers
{
public class Ref<T>
{
public Ref()
{
}
public Ref(T value)
{
Value = value;
}
public T Value { get; set; }
public override string ToString()
{
T value = Value;
return value == null ? string.Empty : value.ToString();
}
public static implicit operator T(Ref<T> r)
{
return r.Value;
}
public static implicit operator Ref<T>(T value)
{
return new Ref<T>(value);
}
}
}
...@@ -15,11 +15,12 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -15,11 +15,12 @@ namespace Titanium.Web.Proxy.Helpers
private static readonly Lazy<bool> isRunningOnMono = new Lazy<bool>(() => Type.GetType("Mono.Runtime") != null); private static readonly Lazy<bool> isRunningOnMono = new Lazy<bool>(() => Type.GetType("Mono.Runtime") != null);
#if NETSTANDARD2_0 #if NETSTANDARD2_0
/// <summary> /// <summary>
/// cache for Windows platform check /// cache for Windows platform check
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
private static readonly Lazy<bool> isRunningOnWindows = new Lazy<bool>(() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); private static readonly Lazy<bool> isRunningOnWindows = new Lazy<bool>(() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
#endif #endif
/// <summary> /// <summary>
......
...@@ -26,7 +26,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -26,7 +26,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary> /// <summary>
/// Both HTTP and HTTPS /// Both HTTP and HTTPS
/// </summary> /// </summary>
AllHttp = Http | Https, AllHttp = Http | Https
} }
internal class HttpSystemProxyValue internal class HttpSystemProxyValue
...@@ -133,7 +133,8 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -133,7 +133,8 @@ namespace Titanium.Web.Proxy.Helpers
reg.DeleteValue(regAutoConfigUrl, false); reg.DeleteValue(regAutoConfigUrl, false);
reg.SetValue(regProxyEnable, 1); reg.SetValue(regProxyEnable, 1);
reg.SetValue(regProxyServer, string.Join(";", existingSystemProxyValues.Select(x => x.ToString()).ToArray())); reg.SetValue(regProxyServer,
string.Join(";", existingSystemProxyValues.Select(x => x.ToString()).ToArray()));
Refresh(); Refresh();
} }
...@@ -162,7 +163,8 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -162,7 +163,8 @@ namespace Titanium.Web.Proxy.Helpers
if (existingSystemProxyValues.Count != 0) if (existingSystemProxyValues.Count != 0)
{ {
reg.SetValue(regProxyEnable, 1); reg.SetValue(regProxyEnable, 1);
reg.SetValue(regProxyServer, string.Join(";", existingSystemProxyValues.Select(x => x.ToString()).ToArray())); reg.SetValue(regProxyServer,
string.Join(";", existingSystemProxyValues.Select(x => x.ToString()).ToArray()));
} }
else else
{ {
...@@ -284,7 +286,8 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -284,7 +286,8 @@ namespace Titanium.Web.Proxy.Helpers
private ProxyInfo GetProxyInfoFromRegistry(RegistryKey reg) private ProxyInfo GetProxyInfoFromRegistry(RegistryKey reg)
{ {
var pi = new ProxyInfo(null, reg.GetValue(regAutoConfigUrl) as string, reg.GetValue(regProxyEnable) as int?, reg.GetValue(regProxyServer) as string, var pi = new ProxyInfo(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.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended.Helpers; using StreamExtended.Helpers;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Network.Tcp;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
internal enum IpVersion internal enum IpVersion
{ {
Ipv4 = 1, Ipv4 = 1,
Ipv6 = 2, Ipv6 = 2
} }
internal class TcpHelper internal class TcpHelper
{ {
/// <summary> /// <summary>
/// Gets the extended TCP table. /// Gets the process id by local port number.
/// </summary> /// </summary>
/// <returns>Collection of <see cref="TcpRow"/>.</returns> /// <returns>Process id.</returns>
internal static TcpTable GetExtendedTcpTable(IpVersion ipVersion) internal static unsafe int GetProcessIdByLocalPort(IpVersion ipVersion, int localPort)
{ {
var tcpRows = new List<TcpRow>();
var tcpTable = IntPtr.Zero; var tcpTable = IntPtr.Zero;
int tcpTableLength = 0; int tcpTableLength = 0;
int ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6; int ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6;
int allPid = (int)NativeMethods.TcpTableType.OwnerPidAll; const int allPid = (int)NativeMethods.TcpTableType.OwnerPidAll;
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, allPid, 0) != 0) if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, allPid, 0) != 0)
{ {
try try
{ {
tcpTable = Marshal.AllocHGlobal(tcpTableLength); tcpTable = Marshal.AllocHGlobal(tcpTableLength);
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, ipVersionValue, allPid, 0) == 0) if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, ipVersionValue, allPid,
0) == 0)
{ {
var table = (NativeMethods.TcpTable)Marshal.PtrToStructure(tcpTable, typeof(NativeMethods.TcpTable)); int rowCount = *(int*)tcpTable;
uint portInNetworkByteOrder = ToNetworkByteOrder((uint)localPort);
var rowPtr = (IntPtr)((long)tcpTable + Marshal.SizeOf(table.length));
for (int i = 0; i < table.length; ++i) if (ipVersion == IpVersion.Ipv4)
{ {
tcpRows.Add(new TcpRow((NativeMethods.TcpRow)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow)))); NativeMethods.TcpRow* rowPtr = (NativeMethods.TcpRow*)(tcpTable + 4);
rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow)));
} for (int i = 0; i < rowCount; ++i)
}
}
finally
{ {
if (tcpTable != IntPtr.Zero) if (rowPtr->localPort == portInNetworkByteOrder)
{ {
Marshal.FreeHGlobal(tcpTable); return rowPtr->owningPid;
}
}
} }
return new TcpTable(tcpRows); rowPtr++;
} }
}
/// <summary> else
/// Gets the TCP row by local port number.
/// </summary>
/// <returns><see cref="TcpRow"/>.</returns>
internal static TcpRow GetTcpRowByLocalPort(IpVersion ipVersion, int localPort)
{
var tcpTable = IntPtr.Zero;
int tcpTableLength = 0;
int ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6;
int allPid = (int)NativeMethods.TcpTableType.OwnerPidAll;
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, allPid, 0) != 0)
{
try
{
tcpTable = Marshal.AllocHGlobal(tcpTableLength);
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, ipVersionValue, allPid, 0) == 0)
{ {
var table = (NativeMethods.TcpTable)Marshal.PtrToStructure(tcpTable, typeof(NativeMethods.TcpTable)); NativeMethods.Tcp6Row* rowPtr = (NativeMethods.Tcp6Row*)(tcpTable + 4);
var rowPtr = (IntPtr)((long)tcpTable + Marshal.SizeOf(table.length));
for (int i = 0; i < table.length; ++i) for (int i = 0; i < rowCount; ++i)
{ {
var tcpRow = (NativeMethods.TcpRow)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow)); if (rowPtr->localPort == portInNetworkByteOrder)
if (tcpRow.GetLocalPort() == localPort)
{ {
return new TcpRow(tcpRow); return rowPtr->owningPid;
} }
rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow))); rowPtr++;
}
} }
} }
} }
...@@ -106,11 +78,24 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -106,11 +78,24 @@ namespace Titanium.Web.Proxy.Helpers
} }
} }
return null; return 0;
} }
/// <summary> /// <summary>
/// relays the input clientStream to the server at the specified host name and port with the given httpCmd and headers as prefix /// Converts 32-bit integer from native byte order (little-endian)
/// to network byte order for port,
/// switches 0th and 1st bytes, and 2nd and 3rd bytes
/// </summary>
/// <param name="port"></param>
/// <returns></returns>
private static uint ToNetworkByteOrder(uint port)
{
return ((port >> 8) & 0x00FF00FFu) | ((port << 8) & 0xFF00FF00u);
}
/// <summary>
/// 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
/// Asynchronous Programming Model, which does not throw exceptions when the socket is closed /// Asynchronous Programming Model, which does not throw exceptions when the socket is closed
/// </summary> /// </summary>
...@@ -119,23 +104,26 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -119,23 +104,26 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="bufferSize"></param> /// <param name="bufferSize"></param>
/// <param name="onDataSend"></param> /// <param name="onDataSend"></param>
/// <param name="onDataReceive"></param> /// <param name="onDataReceive"></param>
/// <param name="cancellationTokenSource"></param>
/// <param name="exceptionFunc"></param> /// <param name="exceptionFunc"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task SendRawApm(Stream clientStream, Stream serverStream, int bufferSize, internal static async Task SendRawApm(Stream clientStream, Stream serverStream, int bufferSize,
Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive, ExceptionHandler exceptionFunc) Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive,
CancellationTokenSource cancellationTokenSource,
ExceptionHandler exceptionFunc)
{ {
var tcs = new TaskCompletionSource<bool>(); var taskCompletionSource = new TaskCompletionSource<bool>();
var cts = new CancellationTokenSource(); cancellationTokenSource.Token.Register(() => taskCompletionSource.TrySetResult(true));
cts.Token.Register(() => tcs.TrySetResult(true));
//Now async relay all server=>client & client=>server data //Now async relay all server=>client & client=>server data
byte[] clientBuffer = BufferPool.GetBuffer(bufferSize); var clientBuffer = BufferPool.GetBuffer(bufferSize);
byte[] serverBuffer = BufferPool.GetBuffer(bufferSize); var serverBuffer = BufferPool.GetBuffer(bufferSize);
try try
{ {
BeginRead(clientStream, serverStream, clientBuffer, cts, onDataSend, exceptionFunc); BeginRead(clientStream, serverStream, clientBuffer, onDataSend, cancellationTokenSource, exceptionFunc);
BeginRead(serverStream, clientStream, serverBuffer, cts, onDataReceive, exceptionFunc); BeginRead(serverStream, clientStream, serverBuffer, onDataReceive, cancellationTokenSource,
await tcs.Task; exceptionFunc);
await taskCompletionSource.Task;
} }
finally finally
{ {
...@@ -144,10 +132,11 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -144,10 +132,11 @@ namespace Titanium.Web.Proxy.Helpers
} }
} }
private static void BeginRead(Stream inputStream, Stream outputStream, byte[] buffer, CancellationTokenSource cts, Action<byte[], int, int> onCopy, private static void BeginRead(Stream inputStream, Stream outputStream, byte[] buffer,
Action<byte[], int, int> onCopy, CancellationTokenSource cancellationTokenSource,
ExceptionHandler exceptionFunc) ExceptionHandler exceptionFunc)
{ {
if (cts.IsCancellationRequested) if (cancellationTokenSource.IsCancellationRequested)
{ {
return; return;
} }
...@@ -155,7 +144,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -155,7 +144,7 @@ namespace Titanium.Web.Proxy.Helpers
bool readFlag = false; bool readFlag = false;
var readCallback = (AsyncCallback)(ar => var readCallback = (AsyncCallback)(ar =>
{ {
if (cts.IsCancellationRequested || readFlag) if (cancellationTokenSource.IsCancellationRequested || readFlag)
{ {
return; return;
} }
...@@ -167,7 +156,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -167,7 +156,7 @@ namespace Titanium.Web.Proxy.Helpers
int read = inputStream.EndRead(ar); int read = inputStream.EndRead(ar);
if (read <= 0) if (read <= 0)
{ {
cts.Cancel(); cancellationTokenSource.Cancel();
return; return;
} }
...@@ -175,7 +164,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -175,7 +164,7 @@ namespace Titanium.Web.Proxy.Helpers
var writeCallback = (AsyncCallback)(ar2 => var writeCallback = (AsyncCallback)(ar2 =>
{ {
if (cts.IsCancellationRequested) if (cancellationTokenSource.IsCancellationRequested)
{ {
return; return;
} }
...@@ -183,11 +172,12 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -183,11 +172,12 @@ namespace Titanium.Web.Proxy.Helpers
try try
{ {
outputStream.EndWrite(ar2); outputStream.EndWrite(ar2);
BeginRead(inputStream, outputStream, buffer, cts, onCopy, exceptionFunc); BeginRead(inputStream, outputStream, buffer, onCopy, cancellationTokenSource,
exceptionFunc);
} }
catch (IOException ex) catch (IOException ex)
{ {
cts.Cancel(); cancellationTokenSource.Cancel();
exceptionFunc(ex); exceptionFunc(ex);
} }
}); });
...@@ -196,7 +186,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -196,7 +186,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
catch (IOException ex) catch (IOException ex)
{ {
cts.Cancel(); cancellationTokenSource.Cancel();
exceptionFunc(ex); exceptionFunc(ex);
} }
}); });
...@@ -209,7 +199,8 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -209,7 +199,8 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <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
/// Task-based Asynchronous Pattern /// Task-based Asynchronous Pattern
/// </summary> /// </summary>
...@@ -218,25 +209,29 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -218,25 +209,29 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="bufferSize"></param> /// <param name="bufferSize"></param>
/// <param name="onDataSend"></param> /// <param name="onDataSend"></param>
/// <param name="onDataReceive"></param> /// <param name="onDataReceive"></param>
/// <param name="cancellationTokenSource"></param>
/// <param name="exceptionFunc"></param> /// <param name="exceptionFunc"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task SendRawTap(Stream clientStream, Stream serverStream, int bufferSize, private static async Task SendRawTap(Stream clientStream, Stream serverStream, int bufferSize,
Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive, ExceptionHandler exceptionFunc) Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive,
CancellationTokenSource cancellationTokenSource,
ExceptionHandler exceptionFunc)
{ {
var cts = new CancellationTokenSource();
//Now async relay all server=>client & client=>server data //Now async relay all server=>client & client=>server data
var sendRelay = clientStream.CopyToAsync(serverStream, onDataSend, bufferSize, cts.Token); var sendRelay =
var receiveRelay = serverStream.CopyToAsync(clientStream, onDataReceive, bufferSize, cts.Token); clientStream.CopyToAsync(serverStream, onDataSend, bufferSize, cancellationTokenSource.Token);
var receiveRelay =
serverStream.CopyToAsync(clientStream, onDataReceive, bufferSize, cancellationTokenSource.Token);
await Task.WhenAny(sendRelay, receiveRelay); await Task.WhenAny(sendRelay, receiveRelay);
cts.Cancel(); cancellationTokenSource.Cancel();
await Task.WhenAll(sendRelay, receiveRelay); await Task.WhenAll(sendRelay, receiveRelay);
} }
/// <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="clientStream"></param> /// <param name="clientStream"></param>
...@@ -244,13 +239,105 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -244,13 +239,105 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="bufferSize"></param> /// <param name="bufferSize"></param>
/// <param name="onDataSend"></param> /// <param name="onDataSend"></param>
/// <param name="onDataReceive"></param> /// <param name="onDataReceive"></param>
/// <param name="cancellationTokenSource"></param>
/// <param name="exceptionFunc"></param> /// <param name="exceptionFunc"></param>
/// <returns></returns> /// <returns></returns>
internal static Task SendRaw(Stream clientStream, Stream serverStream, int bufferSize, internal static Task SendRaw(Stream clientStream, Stream serverStream, int bufferSize,
Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive, ExceptionHandler exceptionFunc) Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive,
CancellationTokenSource cancellationTokenSource,
ExceptionHandler exceptionFunc)
{ {
// todo: fix APM mode // todo: fix APM mode
return SendRawTap(clientStream, serverStream, bufferSize, onDataSend, onDataReceive, exceptionFunc); return SendRawTap(clientStream, serverStream, bufferSize, onDataSend, onDataReceive,
cancellationTokenSource,
exceptionFunc);
}
/// <summary>
/// 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
/// Task-based Asynchronous Pattern
/// </summary>
/// <param name="clientStream"></param>
/// <param name="serverStream"></param>
/// <param name="bufferSize"></param>
/// <param name="onDataSend"></param>
/// <param name="onDataReceive"></param>
/// <param name="cancellationTokenSource"></param>
/// <param name="exceptionFunc"></param>
/// <returns></returns>
internal static async Task SendHttp2(Stream clientStream, Stream serverStream, int bufferSize,
Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive,
CancellationTokenSource cancellationTokenSource,
ExceptionHandler exceptionFunc)
{
var connectionId = Guid.NewGuid();
//Now async relay all server=>client & client=>server data
var sendRelay =
CopyHttp2FrameAsync(clientStream, serverStream, onDataSend, bufferSize, connectionId, cancellationTokenSource.Token);
var receiveRelay =
CopyHttp2FrameAsync(serverStream, clientStream, onDataReceive, bufferSize, connectionId, cancellationTokenSource.Token);
await Task.WhenAny(sendRelay, receiveRelay);
cancellationTokenSource.Cancel();
await Task.WhenAll(sendRelay, receiveRelay);
}
private static async Task CopyHttp2FrameAsync(Stream input, Stream output, Action<byte[], int, int> onCopy,
int bufferSize, Guid connectionId, CancellationToken cancellationToken)
{
var headerBuffer = new byte[9];
var buffer = new byte[32768];
while (true)
{
int read = await ForceRead(input, headerBuffer, 0, 9, cancellationToken);
if (read != 9)
{
return;
}
int length = (headerBuffer[0] << 16) + (headerBuffer[1] << 8) + headerBuffer[2];
byte type = headerBuffer[3];
byte flags = headerBuffer[4];
int streamId = ((headerBuffer[5] & 0x7f) << 24) + (headerBuffer[6] << 16) + (headerBuffer[7] << 8) + headerBuffer[8];
read = await ForceRead(input, buffer, 0, length, cancellationToken);
if (read != length)
{
return;
}
await output.WriteAsync(headerBuffer, 0, headerBuffer.Length, cancellationToken);
await output.WriteAsync(buffer, 0, length, cancellationToken);
/*using (var fs = new System.IO.FileStream($@"c:\11\{connectionId}.{streamId}.dat", FileMode.Append))
{
fs.Write(headerBuffer, 0, headerBuffer.Length);
fs.Write(buffer, 0, length);
}*/
}
}
private static async Task<int> ForceRead(Stream input, byte[] buffer, int offset, int bytesToRead, CancellationToken cancellationToken)
{
int totalRead = 0;
while (bytesToRead > 0)
{
int read = await input.ReadAsync(buffer, offset, bytesToRead, cancellationToken);
if (read == -1)
{
break;
}
totalRead += read;
bytesToRead -= read;
offset += read;
}
return totalRead;
} }
} }
} }
...@@ -4,21 +4,25 @@ using System.Runtime.InteropServices; ...@@ -4,21 +4,25 @@ using System.Runtime.InteropServices;
// Helper classes for setting system proxy settings // Helper classes for setting system proxy settings
namespace Titanium.Web.Proxy.Helpers.WinHttp namespace Titanium.Web.Proxy.Helpers.WinHttp
{ {
internal partial class NativeMethods internal class NativeMethods
{ {
internal static class WinHttp internal static class WinHttp
{ {
[DllImport("winhttp.dll", SetLastError = true)] [DllImport("winhttp.dll", SetLastError = true)]
internal static extern bool WinHttpGetIEProxyConfigForCurrentUser(ref WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig); internal static extern bool WinHttpGetIEProxyConfigForCurrentUser(
ref WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig);
[DllImport("winhttp.dll", CharSet = CharSet.Unicode, SetLastError = true)] [DllImport("winhttp.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern WinHttpHandle WinHttpOpen(string userAgent, AccessType accessType, string proxyName, string proxyBypass, int dwFlags); internal static extern WinHttpHandle WinHttpOpen(string userAgent, AccessType accessType, string proxyName,
string proxyBypass, int dwFlags);
[DllImport("winhttp.dll", CharSet = CharSet.Unicode, SetLastError = true)] [DllImport("winhttp.dll", CharSet = CharSet.Unicode, SetLastError = true)]
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, internal static extern bool WinHttpGetProxyForUrl(WinHttpHandle session, string url,
[In] ref WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions,
out WINHTTP_PROXY_INFO proxyInfo); out WINHTTP_PROXY_INFO proxyInfo);
[DllImport("winhttp.dll", CharSet = CharSet.Unicode, SetLastError = true)] [DllImport("winhttp.dll", CharSet = CharSet.Unicode, SetLastError = true)]
......
...@@ -9,11 +9,11 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp ...@@ -9,11 +9,11 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
{ {
} }
public override bool IsInvalid => handle == IntPtr.Zero;
protected override bool ReleaseHandle() protected override bool ReleaseHandle()
{ {
return NativeMethods.WinHttp.WinHttpCloseHandle(handle); return NativeMethods.WinHttp.WinHttpCloseHandle(handle);
} }
public override bool IsInvalid => handle == IntPtr.Zero;
} }
} }
...@@ -14,6 +14,26 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp ...@@ -14,6 +14,26 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
private bool autoDetectFailed; private bool autoDetectFailed;
private AutoWebProxyState state; private AutoWebProxyState state;
public WinHttpWebProxyFinder()
{
session = NativeMethods.WinHttp.WinHttpOpen(null, NativeMethods.WinHttp.AccessType.NoProxy, null, null, 0);
if (session == null || session.IsInvalid)
{
int lastWin32Error = GetLastWin32Error();
}
else
{
int downloadTimeout = 60 * 1000;
if (NativeMethods.WinHttp.WinHttpSetTimeouts(session, downloadTimeout, downloadTimeout, downloadTimeout,
downloadTimeout))
{
return;
}
int lastWin32Error = GetLastWin32Error();
}
}
public ICredentials Credentials { get; set; } public ICredentials Credentials { get; set; }
public ProxyInfo ProxyInfo { get; internal set; } public ProxyInfo ProxyInfo { get; internal set; }
...@@ -28,27 +48,18 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp ...@@ -28,27 +48,18 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
private WebProxy proxy { get; set; } private WebProxy proxy { get; set; }
public WinHttpWebProxyFinder() public void Dispose()
{
session = NativeMethods.WinHttp.WinHttpOpen(null, NativeMethods.WinHttp.AccessType.NoProxy, null, null, 0);
if (session == null || session.IsInvalid)
{
int lastWin32Error = GetLastWin32Error();
}
else
{ {
int downloadTimeout = 60 * 1000; Dispose(true);
if (NativeMethods.WinHttp.WinHttpSetTimeouts(session, downloadTimeout, downloadTimeout, downloadTimeout, downloadTimeout))
return;
int lastWin32Error = GetLastWin32Error();
}
} }
public bool GetAutoProxies(Uri destination, out IList<string> proxyList) public bool GetAutoProxies(Uri destination, out IList<string> proxyList)
{ {
proxyList = null; proxyList = null;
if (session == null || session.IsInvalid || state == AutoWebProxyState.UnrecognizedScheme) if (session == null || session.IsInvalid || state == AutoWebProxyState.UnrecognizedScheme)
{
return false; return false;
}
string proxyListString = null; string proxyListString = null;
var errorCode = NativeMethods.WinHttp.ErrorCodes.AudodetectionFailed; var errorCode = NativeMethods.WinHttp.ErrorCodes.AudodetectionFailed;
...@@ -64,11 +75,16 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp ...@@ -64,11 +75,16 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
} }
if (AutomaticConfigurationScript != null && IsRecoverableAutoProxyError(errorCode)) if (AutomaticConfigurationScript != null && IsRecoverableAutoProxyError(errorCode))
errorCode = (NativeMethods.WinHttp.ErrorCodes)GetAutoProxies(destination, AutomaticConfigurationScript, out proxyListString); {
errorCode = (NativeMethods.WinHttp.ErrorCodes)GetAutoProxies(destination, AutomaticConfigurationScript,
out proxyListString);
}
state = GetStateFromErrorCode(errorCode); state = GetStateFromErrorCode(errorCode);
if (state != AutoWebProxyState.Completed) if (state != AutoWebProxyState.Completed)
{
return false; return false;
}
if (!string.IsNullOrEmpty(proxyListString)) if (!string.IsNullOrEmpty(proxyListString))
{ {
...@@ -101,14 +117,16 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp ...@@ -101,14 +117,16 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
var systemProxy = new ExternalProxy var systemProxy = new ExternalProxy
{ {
HostName = proxyStr, 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);
if (protocolType.HasValue) if (protocolType.HasValue)
...@@ -119,7 +137,7 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp ...@@ -119,7 +137,7 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
var systemProxy = new ExternalProxy var systemProxy = new ExternalProxy
{ {
HostName = value.HostName, HostName = value.HostName,
Port = value.Port, Port = value.Port
}; };
return systemProxy; return systemProxy;
...@@ -159,7 +177,9 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp ...@@ -159,7 +177,9 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
else else
{ {
if (Marshal.GetLastWin32Error() == 8) if (Marshal.GetLastWin32Error() == 8)
{
throw new OutOfMemoryException(); throw new OutOfMemoryException();
}
result = new ProxyInfo(true, null, null, null, null); result = new ProxyInfo(true, null, null, null, null);
} }
...@@ -180,15 +200,13 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp ...@@ -180,15 +200,13 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
autoDetectFailed = false; autoDetectFailed = false;
} }
public void Dispose()
{
Dispose(true);
}
private void Dispose(bool disposing) private void Dispose(bool disposing)
{ {
if (!disposing || session == null || session.IsInvalid) if (!disposing || session == null || session.IsInvalid)
{
return; return;
}
session.Close(); session.Close();
} }
...@@ -201,7 +219,8 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp ...@@ -201,7 +219,8 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
{ {
autoProxyOptions.Flags = NativeMethods.WinHttp.AutoProxyFlags.AutoDetect; autoProxyOptions.Flags = NativeMethods.WinHttp.AutoProxyFlags.AutoDetect;
autoProxyOptions.AutoConfigUrl = null; autoProxyOptions.AutoConfigUrl = null;
autoProxyOptions.AutoDetectFlags = NativeMethods.WinHttp.AutoDetectType.Dhcp | NativeMethods.WinHttp.AutoDetectType.DnsA; autoProxyOptions.AutoDetectFlags =
NativeMethods.WinHttp.AutoDetectType.Dhcp | NativeMethods.WinHttp.AutoDetectType.DnsA;
} }
else else
{ {
...@@ -218,14 +237,17 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp ...@@ -218,14 +237,17 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
{ {
autoProxyOptions.AutoLogonIfChallenged = true; autoProxyOptions.AutoLogonIfChallenged = true;
if (!WinHttpGetProxyForUrl(destination.ToString(), ref autoProxyOptions, out proxyListString)) if (!WinHttpGetProxyForUrl(destination.ToString(), ref autoProxyOptions, out proxyListString))
{
num = GetLastWin32Error(); num = GetLastWin32Error();
} }
} }
}
return num; return num;
} }
private bool WinHttpGetProxyForUrl(string destination, ref NativeMethods.WinHttp.WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions, out string proxyListString) private bool WinHttpGetProxyForUrl(string destination,
ref NativeMethods.WinHttp.WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions, out string proxyListString)
{ {
proxyListString = null; proxyListString = null;
bool flag; bool flag;
...@@ -233,15 +255,19 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp ...@@ -233,15 +255,19 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
RuntimeHelpers.PrepareConstrainedRegions(); RuntimeHelpers.PrepareConstrainedRegions();
try try
{ {
flag = NativeMethods.WinHttp.WinHttpGetProxyForUrl(session, destination, ref autoProxyOptions, out proxyInfo); flag = NativeMethods.WinHttp.WinHttpGetProxyForUrl(session, destination, ref autoProxyOptions,
out proxyInfo);
if (flag) if (flag)
{
proxyListString = Marshal.PtrToStringUni(proxyInfo.Proxy); proxyListString = Marshal.PtrToStringUni(proxyInfo.Proxy);
} }
}
finally finally
{ {
Marshal.FreeHGlobal(proxyInfo.Proxy); Marshal.FreeHGlobal(proxyInfo.Proxy);
Marshal.FreeHGlobal(proxyInfo.ProxyBypass); Marshal.FreeHGlobal(proxyInfo.ProxyBypass);
} }
return flag; return flag;
} }
...@@ -249,7 +275,10 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp ...@@ -249,7 +275,10 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
{ {
int lastWin32Error = Marshal.GetLastWin32Error(); int lastWin32Error = Marshal.GetLastWin32Error();
if (lastWin32Error == 8) if (lastWin32Error == 8)
{
throw new OutOfMemoryException(); throw new OutOfMemoryException();
}
return lastWin32Error; return lastWin32Error;
} }
...@@ -274,7 +303,10 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp ...@@ -274,7 +303,10 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
private static AutoWebProxyState GetStateFromErrorCode(NativeMethods.WinHttp.ErrorCodes errorCode) private static AutoWebProxyState GetStateFromErrorCode(NativeMethods.WinHttp.ErrorCodes errorCode)
{ {
if (errorCode == 0L) if (errorCode == 0L)
{
return AutoWebProxyState.Completed; return AutoWebProxyState.Completed;
}
switch (errorCode) switch (errorCode)
{ {
case NativeMethods.WinHttp.ErrorCodes.UnableToDownloadScript: case NativeMethods.WinHttp.ErrorCodes.UnableToDownloadScript:
...@@ -298,8 +330,10 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp ...@@ -298,8 +330,10 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
foreach (char c in value) foreach (char c in value)
{ {
if (!char.IsWhiteSpace(c)) if (!char.IsWhiteSpace(c))
{
stringBuilder.Append(c); stringBuilder.Append(c);
} }
}
return stringBuilder.ToString(); return stringBuilder.ToString();
} }
...@@ -325,7 +359,7 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp ...@@ -325,7 +359,7 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
DownloadFailure, DownloadFailure,
CompilationFailure, CompilationFailure,
UnrecognizedScheme, UnrecognizedScheme,
Completed, Completed
} }
} }
} }
...@@ -4,11 +4,11 @@ namespace Titanium.Web.Proxy.Http ...@@ -4,11 +4,11 @@ namespace Titanium.Web.Proxy.Http
{ {
public class ConnectRequest : Request public class ConnectRequest : Request
{ {
public ClientHelloInfo ClientHelloInfo { get; set; }
public ConnectRequest() public ConnectRequest()
{ {
Method = "CONNECT"; Method = "CONNECT";
} }
public ClientHelloInfo ClientHelloInfo { get; set; }
} }
} }
using StreamExtended; using System;
using System;
using System.Net; using System.Net;
using StreamExtended;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
......
...@@ -15,6 +15,17 @@ namespace Titanium.Web.Proxy.Http ...@@ -15,6 +15,17 @@ namespace Titanium.Web.Proxy.Http
private readonly Dictionary<string, List<HttpHeader>> nonUniqueHeaders; private readonly Dictionary<string, List<HttpHeader>> nonUniqueHeaders;
/// <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);
Headers = new ReadOnlyDictionary<string, HttpHeader>(headers);
NonUniqueHeaders = new ReadOnlyDictionary<string, List<HttpHeader>>(nonUniqueHeaders);
}
/// <summary> /// <summary>
/// Unique Request header collection /// Unique Request header collection
/// </summary> /// </summary>
...@@ -26,14 +37,19 @@ namespace Titanium.Web.Proxy.Http ...@@ -26,14 +37,19 @@ namespace Titanium.Web.Proxy.Http
public ReadOnlyDictionary<string, List<HttpHeader>> NonUniqueHeaders { get; } public ReadOnlyDictionary<string, List<HttpHeader>> NonUniqueHeaders { get; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HeaderCollection"/> class. /// Returns an enumerator that iterates through the collection.
/// </summary> /// </summary>
public HeaderCollection() /// <returns>
/// An enumerator that can be used to iterate through the collection.
/// </returns>
public IEnumerator<HttpHeader> GetEnumerator()
{ {
headers = new Dictionary<string, HttpHeader>(StringComparer.OrdinalIgnoreCase); return headers.Values.Concat(nonUniqueHeaders.Values.SelectMany(x => x)).GetEnumerator();
nonUniqueHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase); }
Headers = new ReadOnlyDictionary<string, HttpHeader>(headers);
NonUniqueHeaders = new ReadOnlyDictionary<string, List<HttpHeader>>(nonUniqueHeaders); IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
} }
/// <summary> /// <summary>
...@@ -190,7 +206,8 @@ namespace Titanium.Web.Proxy.Http ...@@ -190,7 +206,8 @@ namespace Titanium.Web.Proxy.Http
{ {
if (header.Key != header.Value.Name) if (header.Key != header.Value.Name)
{ {
throw new Exception("Header name mismatch. Key and the name of the HttpHeader object should be the same."); throw new Exception(
"Header name mismatch. Key and the name of the HttpHeader object should be the same.");
} }
AddHeader(header.Value); AddHeader(header.Value);
...@@ -201,8 +218,10 @@ namespace Titanium.Web.Proxy.Http ...@@ -201,8 +218,10 @@ namespace Titanium.Web.Proxy.Http
/// removes all headers with given name /// removes all headers with given name
/// </summary> /// </summary>
/// <param name="headerName"></param> /// <param name="headerName"></param>
/// <returns>True if header was removed /// <returns>
/// False if no header exists with given name</returns> /// True if header was removed
/// False if no header exists with given name
/// </returns>
public bool RemoveHeader(string headerName) public bool RemoveHeader(string headerName)
{ {
bool result = headers.Remove(headerName); bool result = headers.Remove(headerName);
...@@ -286,21 +305,5 @@ namespace Titanium.Web.Proxy.Http ...@@ -286,21 +305,5 @@ namespace Titanium.Web.Proxy.Http
SetOrAddHeaderValue(KnownHeaders.Connection, proxyHeader); SetOrAddHeaderValue(KnownHeaders.Connection, proxyHeader);
} }
} }
/// <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();
}
} }
} }
using System; using System;
using System.Collections.Generic; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended.Network; using StreamExtended.Network;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
internal static class HeaderParser internal static class HeaderParser
{ {
internal static async Task ReadHeaders(CustomBinaryReader reader, HeaderCollection headerCollection) internal static async Task ReadHeaders(CustomBinaryReader reader, HeaderCollection headerCollection, CancellationToken cancellationToken)
{ {
string tmpLine; string tmpLine;
while (!string.IsNullOrEmpty(tmpLine = await reader.ReadLineAsync())) while (!string.IsNullOrEmpty(tmpLine = await reader.ReadLineAsync(cancellationToken)))
{ {
var header = tmpLine.Split(ProxyConstants.ColonSplit, 2); var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);
headerCollection.AddHeader(header[0], header[1]); headerCollection.AddHeader(header[0], header[1]);
......
using System; using System;
using System.IO; using System.IO;
using System.Net; using System.Net;
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.Models;
...@@ -15,6 +16,15 @@ namespace Titanium.Web.Proxy.Http ...@@ -15,6 +16,15 @@ namespace Titanium.Web.Proxy.Http
{ {
private readonly int bufferSize; private readonly int bufferSize;
internal HttpWebClient(int bufferSize, Request request = null, Response response = null)
{
this.bufferSize = bufferSize;
RequestId = Guid.NewGuid();
Request = request ?? new Request();
Response = response ?? new Response();
}
/// <summary> /// <summary>
/// Connection to server /// Connection to server
/// </summary> /// </summary>
...@@ -56,19 +66,10 @@ namespace Titanium.Web.Proxy.Http ...@@ -56,19 +66,10 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
public bool IsHttps => Request.IsHttps; public bool IsHttps => Request.IsHttps;
internal HttpWebClient(int bufferSize, Request request = null, Response response = null)
{
this.bufferSize = bufferSize;
RequestId = Guid.NewGuid();
Request = request ?? new Request();
Response = response ?? new Response();
}
/// <summary> /// <summary>
/// Set the tcp connection to server used by this webclient /// Set the tcp connection to server used by this webclient
/// </summary> /// </summary>
/// <param name="connection">Instance of <see cref="TcpConnection"/></param> /// <param name="connection">Instance of <see cref="TcpConnection" /></param>
internal void SetConnection(TcpConnection connection) internal void SetConnection(TcpConnection connection)
{ {
connection.LastAccess = DateTime.Now; connection.LastAccess = DateTime.Now;
...@@ -79,7 +80,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -79,7 +80,7 @@ namespace Titanium.Web.Proxy.Http
/// Prepare and send the http(s) request /// Prepare and send the http(s) request
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
internal async Task SendRequest(bool enable100ContinueBehaviour, bool isTransparent) internal async Task SendRequest(bool enable100ContinueBehaviour, bool isTransparent, CancellationToken cancellationToken)
{ {
var upstreamProxy = ServerConnection.UpStreamProxy; var upstreamProxy = ServerConnection.UpStreamProxy;
...@@ -90,7 +91,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -90,7 +91,7 @@ namespace Titanium.Web.Proxy.Http
//prepare the request & headers //prepare the request & headers
await writer.WriteLineAsync(Request.CreateRequestLine(Request.Method, await writer.WriteLineAsync(Request.CreateRequestLine(Request.Method,
useUpstreamProxy || isTransparent ? Request.OriginalUrl : Request.RequestUri.PathAndQuery, useUpstreamProxy || isTransparent ? Request.OriginalUrl : Request.RequestUri.PathAndQuery,
Request.HttpVersion)); Request.HttpVersion), cancellationToken);
//Send Authentication to Upstream proxy if needed //Send Authentication to Upstream proxy if needed
...@@ -99,8 +100,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -99,8 +100,9 @@ namespace Titanium.Web.Proxy.Http
&& !string.IsNullOrEmpty(upstreamProxy.UserName) && !string.IsNullOrEmpty(upstreamProxy.UserName)
&& upstreamProxy.Password != null) && upstreamProxy.Password != null)
{ {
await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer); await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer, cancellationToken);
await HttpHeader.GetProxyAuthorizationHeader(upstreamProxy.UserName, upstreamProxy.Password).WriteToStreamAsync(writer); await HttpHeader.GetProxyAuthorizationHeader(upstreamProxy.UserName, upstreamProxy.Password)
.WriteToStreamAsync(writer, cancellationToken);
} }
//write request headers //write request headers
...@@ -108,32 +110,33 @@ namespace Titanium.Web.Proxy.Http ...@@ -108,32 +110,33 @@ namespace Titanium.Web.Proxy.Http
{ {
if (isTransparent || header.Name != KnownHeaders.ProxyAuthorization) if (isTransparent || header.Name != KnownHeaders.ProxyAuthorization)
{ {
await header.WriteToStreamAsync(writer); await header.WriteToStreamAsync(writer, cancellationToken);
} }
} }
await writer.WriteLineAsync(); await writer.WriteLineAsync(cancellationToken);
if (enable100ContinueBehaviour) if (enable100ContinueBehaviour)
{ {
if (Request.ExpectContinue) if (Request.ExpectContinue)
{ {
string httpStatus = await ServerConnection.StreamReader.ReadLineAsync(); string httpStatus = await ServerConnection.StreamReader.ReadLineAsync(cancellationToken);
Response.ParseResponseLine(httpStatus, out _, out int responseStatusCode, out string responseStatusDescription); Response.ParseResponseLine(httpStatus, out _, out int responseStatusCode,
out string responseStatusDescription);
//find if server is willing for expect continue //find if server is willing for expect continue
if (responseStatusCode == (int)HttpStatusCode.Continue if (responseStatusCode == (int)HttpStatusCode.Continue
&& responseStatusDescription.EqualsIgnoreCase("continue")) && responseStatusDescription.EqualsIgnoreCase("continue"))
{ {
Request.Is100Continue = true; Request.Is100Continue = true;
await ServerConnection.StreamReader.ReadLineAsync(); await ServerConnection.StreamReader.ReadLineAsync(cancellationToken);
} }
else if (responseStatusCode == (int)HttpStatusCode.ExpectationFailed else if (responseStatusCode == (int)HttpStatusCode.ExpectationFailed
&& responseStatusDescription.EqualsIgnoreCase("expectation failed")) && responseStatusDescription.EqualsIgnoreCase("expectation failed"))
{ {
Request.ExpectationFailed = true; Request.ExpectationFailed = true;
await ServerConnection.StreamReader.ReadLineAsync(); await ServerConnection.StreamReader.ReadLineAsync(cancellationToken);
} }
} }
} }
...@@ -143,13 +146,15 @@ namespace Titanium.Web.Proxy.Http ...@@ -143,13 +146,15 @@ namespace Titanium.Web.Proxy.Http
/// Receive and parse the http response from server /// Receive and parse the http response from server
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
internal async Task ReceiveResponse() internal async Task ReceiveResponse(CancellationToken cancellationToken)
{ {
//return if this is already read //return if this is already read
if (Response.StatusCode != 0) if (Response.StatusCode != 0)
{
return; return;
}
string httpStatus = await ServerConnection.StreamReader.ReadLineAsync(); string httpStatus = await ServerConnection.StreamReader.ReadLineAsync(cancellationToken);
if (httpStatus == null) if (httpStatus == null)
{ {
throw new IOException(); throw new IOException();
...@@ -157,8 +162,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -157,8 +162,7 @@ namespace Titanium.Web.Proxy.Http
if (httpStatus == string.Empty) if (httpStatus == string.Empty)
{ {
//Empty content in first-line, try again httpStatus = await ServerConnection.StreamReader.ReadLineAsync(cancellationToken);
httpStatus = await ServerConnection.StreamReader.ReadLineAsync();
} }
Response.ParseResponseLine(httpStatus, out var version, out int statusCode, out string statusDescription); Response.ParseResponseLine(httpStatus, out var version, out int statusCode, out string statusDescription);
...@@ -174,10 +178,10 @@ namespace Titanium.Web.Proxy.Http ...@@ -174,10 +178,10 @@ namespace Titanium.Web.Proxy.Http
//Read the next line after 100-continue //Read the next line after 100-continue
Response.Is100Continue = true; Response.Is100Continue = true;
Response.StatusCode = 0; Response.StatusCode = 0;
await ServerConnection.StreamReader.ReadLineAsync(); await ServerConnection.StreamReader.ReadLineAsync(cancellationToken);
//now receive response //now receive response
await ReceiveResponse(); await ReceiveResponse(cancellationToken);
return; return;
} }
...@@ -187,15 +191,15 @@ namespace Titanium.Web.Proxy.Http ...@@ -187,15 +191,15 @@ namespace Titanium.Web.Proxy.Http
//read next line after expectation failed response //read next line after expectation failed response
Response.ExpectationFailed = true; Response.ExpectationFailed = true;
Response.StatusCode = 0; Response.StatusCode = 0;
await ServerConnection.StreamReader.ReadLineAsync(); await ServerConnection.StreamReader.ReadLineAsync(cancellationToken);
//now receive response //now receive response
await ReceiveResponse(); await ReceiveResponse(cancellationToken);
return; return;
} }
//Read the response headers in to unique and non-unique header collections //Read the response headers in to unique and non-unique header collections
await HeaderParser.ReadHeaders(ServerConnection.StreamReader, Response.Headers); await HeaderParser.ReadHeaders(ServerConnection.StreamReader, Response.Headers, cancellationToken);
} }
/// <summary> /// <summary>
......
using System; namespace Titanium.Web.Proxy.Http
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Http
{ {
public static class KnownHeaders public static class KnownHeaders
{ {
......
...@@ -100,36 +100,6 @@ namespace Titanium.Web.Proxy.Http ...@@ -100,36 +100,6 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
internal bool CancelRequest { get; set; } internal bool CancelRequest { get; set; }
internal override void EnsureBodyAvailable(bool throwWhenNotReadYet = true)
{
if (BodyInternal != null)
{
return;
}
//GET request don't have a request body to read
if (!HasBody)
{
throw new BodyNotFoundException("Request don't have a body. " + "Please verify that this request is a Http POST/PUT/PATCH and request " +
"content length is greater than zero before accessing the body.");
}
if (!IsBodyRead)
{
if (Locked)
{
throw new Exception("You cannot get the request body after request is made to server.");
}
if (throwWhenNotReadYet)
{
throw new Exception("Request body is not read yet. " +
"Use SessionEventArgs.GetRequestBody() or SessionEventArgs.GetRequestBodyAsString() " +
"method to read the request body.");
}
}
}
/// <summary> /// <summary>
/// Does this request has an upgrade to websocket header? /// Does this request has an upgrade to websocket header?
/// </summary> /// </summary>
...@@ -177,12 +147,44 @@ namespace Titanium.Web.Proxy.Http ...@@ -177,12 +147,44 @@ namespace Titanium.Web.Proxy.Http
} }
} }
internal override void EnsureBodyAvailable(bool throwWhenNotReadYet = true)
{
if (BodyInternal != null)
{
return;
}
//GET request don't have a request body to read
if (!HasBody)
{
throw new BodyNotFoundException("Request don't have a body. " +
"Please verify that this request is a Http POST/PUT/PATCH and request " +
"content length is greater than zero before accessing the body.");
}
if (!IsBodyRead)
{
if (Locked)
{
throw new Exception("You cannot get the request body after request is made to server.");
}
if (throwWhenNotReadYet)
{
throw new Exception("Request body is not read yet. " +
"Use SessionEventArgs.GetRequestBody() or SessionEventArgs.GetRequestBodyAsString() " +
"method to read the request body.");
}
}
}
internal static string CreateRequestLine(string httpMethod, string httpUrl, Version version) internal static string CreateRequestLine(string httpMethod, string httpUrl, Version version)
{ {
return $"{httpMethod} {httpUrl} HTTP/{version.Major}.{version.Minor}"; return $"{httpMethod} {httpUrl} HTTP/{version.Major}.{version.Minor}";
} }
internal static void ParseRequestLine(string httpCmd, out string httpMethod, out string httpUrl, out Version version) internal static void ParseRequestLine(string httpCmd, out string httpMethod, out string httpUrl,
out Version version)
{ {
//break up the line into three components (method, remote URL & Http Version) //break up the line into three components (method, remote URL & Http Version)
var httpCmdSplit = httpCmd.Split(ProxyConstants.SpaceSplit, 3); var httpCmdSplit = httpCmd.Split(ProxyConstants.SpaceSplit, 3);
...@@ -196,11 +198,6 @@ namespace Titanium.Web.Proxy.Http ...@@ -196,11 +198,6 @@ namespace Titanium.Web.Proxy.Http
httpMethod = httpCmdSplit[0]; httpMethod = httpCmdSplit[0];
if (!IsAllUpper(httpMethod)) if (!IsAllUpper(httpMethod))
{ {
//method should be upper cased: https://tools.ietf.org/html/rfc7231#section-4
//todo: create protocol violation message
//fix it
httpMethod = httpMethod.ToUpper(); httpMethod = httpMethod.ToUpper();
} }
......
using System; using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using Titanium.Web.Proxy.Compression; using Titanium.Web.Proxy.Compression;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
...@@ -23,6 +21,11 @@ namespace Titanium.Web.Proxy.Http ...@@ -23,6 +21,11 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
private string bodyString; private string bodyString;
/// <summary>
/// Store weather the original request/response has body or not, since the user may change the parameters
/// </summary>
internal bool OriginalHasBody;
/// <summary> /// <summary>
/// Keeps the body data after the session is finished /// Keeps the body data after the session is finished
/// </summary> /// </summary>
...@@ -145,9 +148,21 @@ namespace Titanium.Web.Proxy.Http ...@@ -145,9 +148,21 @@ namespace Titanium.Web.Proxy.Http
public abstract bool HasBody { get; } public abstract bool HasBody { get; }
/// <summary> /// <summary>
/// Store weather the original request/response has body or not, since the user may change the parameters /// Body as string
/// Use the encoding specified to decode the byte[] data to string
/// </summary> /// </summary>
internal bool OriginalHasBody; [Browsable(false)]
public string BodyString => bodyString ?? (bodyString = Encoding.GetString(Body));
/// <summary>
/// Was the body read by user?
/// </summary>
public bool IsBodyRead { get; internal set; }
/// <summary>
/// Is the request/response no more modifyable by user (user callbacks complete?)
/// </summary>
internal bool Locked { get; set; }
internal abstract void EnsureBodyAvailable(bool throwWhenNotReadYet = true); internal abstract void EnsureBodyAvailable(bool throwWhenNotReadYet = true);
...@@ -205,23 +220,6 @@ namespace Titanium.Web.Proxy.Http ...@@ -205,23 +220,6 @@ namespace Titanium.Web.Proxy.Http
return null; return null;
} }
/// <summary>
/// Body as string
/// Use the encoding specified to decode the byte[] data to string
/// </summary>
[Browsable(false)]
public string BodyString => bodyString ?? (bodyString = Encoding.GetString(Body));
/// <summary>
/// Was the body read by user?
/// </summary>
public bool IsBodyRead { get; internal set; }
/// <summary>
/// Is the request/response no more modifyable by user (user callbacks complete?)
/// </summary>
internal bool Locked { get; set; }
internal void UpdateContentLength() internal void UpdateContentLength()
{ {
ContentLength = IsChunked ? -1 : BodyInternal?.Length ?? 0; ContentLength = IsChunked ? -1 : BodyInternal?.Length ?? 0;
......
...@@ -13,6 +13,21 @@ namespace Titanium.Web.Proxy.Http ...@@ -13,6 +13,21 @@ namespace Titanium.Web.Proxy.Http
[TypeConverter(typeof(ExpandableObjectConverter))] [TypeConverter(typeof(ExpandableObjectConverter))]
public class Response : RequestResponseBase public class Response : RequestResponseBase
{ {
/// <summary>
/// Constructor.
/// </summary>
public Response()
{
}
/// <summary>
/// Constructor.
/// </summary>
public Response(byte[] body)
{
Body = body;
}
/// <summary> /// <summary>
/// Response Status Code. /// Response Status Code.
/// </summary> /// </summary>
...@@ -79,21 +94,6 @@ namespace Titanium.Web.Proxy.Http ...@@ -79,21 +94,6 @@ namespace Titanium.Web.Proxy.Http
internal bool TerminateResponse { get; set; } internal bool TerminateResponse { get; set; }
internal override void EnsureBodyAvailable(bool throwWhenNotReadYet = true)
{
if (BodyInternal != null)
{
return;
}
if (!IsBodyRead && throwWhenNotReadYet)
{
throw new Exception("Response body is not read yet. " +
"Use SessionEventArgs.GetResponseBody() or SessionEventArgs.GetResponseBodyAsString() " +
"method to read the response body.");
}
}
/// <summary> /// <summary>
/// Is response 100-continue /// Is response 100-continue
/// </summary> /// </summary>
...@@ -104,11 +104,6 @@ namespace Titanium.Web.Proxy.Http ...@@ -104,11 +104,6 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
public bool ExpectationFailed { get; internal set; } public bool ExpectationFailed { get; internal set; }
/// <summary>
/// Gets the resposne status.
/// </summary>
public string Status => CreateResponseLine(HttpVersion, StatusCode, StatusDescription);
/// <summary> /// <summary>
/// Gets the header text. /// Gets the header text.
/// </summary> /// </summary>
...@@ -117,7 +112,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -117,7 +112,7 @@ namespace Titanium.Web.Proxy.Http
get get
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.AppendLine(Status); sb.AppendLine(CreateResponseLine(HttpVersion, StatusCode, StatusDescription));
foreach (var header in Headers) foreach (var header in Headers)
{ {
sb.AppendLine(header.ToString()); sb.AppendLine(header.ToString());
...@@ -128,19 +123,19 @@ namespace Titanium.Web.Proxy.Http ...@@ -128,19 +123,19 @@ namespace Titanium.Web.Proxy.Http
} }
} }
/// <summary> internal override void EnsureBodyAvailable(bool throwWhenNotReadYet = true)
/// Constructor.
/// </summary>
public Response()
{ {
if (BodyInternal != null)
{
return;
} }
/// <summary> if (!IsBodyRead && throwWhenNotReadYet)
/// Constructor.
/// </summary>
public Response(byte[] body)
{ {
Body = body; throw new Exception("Response body is not read yet. " +
"Use SessionEventArgs.GetResponseBody() or SessionEventArgs.GetResponseBodyAsString() " +
"method to read the response body.");
}
} }
internal static string CreateResponseLine(Version version, int statusCode, string statusDescription) internal static string CreateResponseLine(Version version, int statusCode, string statusDescription)
...@@ -148,7 +143,8 @@ namespace Titanium.Web.Proxy.Http ...@@ -148,7 +143,8 @@ namespace Titanium.Web.Proxy.Http
return $"HTTP/{version.Major}.{version.Minor} {statusCode} {statusDescription}"; return $"HTTP/{version.Major}.{version.Minor} {statusCode} {statusDescription}";
} }
internal static void ParseResponseLine(string httpStatus, out Version version, out int statusCode, out string statusDescription) internal static void ParseResponseLine(string httpStatus, out Version version, out int statusCode,
out string statusDescription)
{ {
var httpResult = httpStatus.Split(ProxyConstants.SpaceSplit, 3); var httpResult = httpStatus.Split(ProxyConstants.SpaceSplit, 3);
if (httpResult.Length != 3) if (httpResult.Length != 3)
......
using System.Net; using System.Net;
using System.Web;
namespace Titanium.Web.Proxy.Http.Responses namespace Titanium.Web.Proxy.Http.Responses
{ {
...@@ -15,9 +16,13 @@ namespace Titanium.Web.Proxy.Http.Responses ...@@ -15,9 +16,13 @@ namespace Titanium.Web.Proxy.Http.Responses
{ {
StatusCode = (int)status; StatusCode = (int)status;
#if NET45
StatusDescription = HttpWorkerRequest.GetStatusDescription(StatusCode);
#else
//todo: this is not really correct, status description should contain spaces, too //todo: this is not really correct, status description should contain spaces, too
//see: https://tools.ietf.org/html/rfc7231#section-6.1 //see: https://tools.ietf.org/html/rfc7231#section-6.1
StatusDescription = status.ToString(); StatusDescription = status.ToString();
#endif
} }
/// <summary> /// <summary>
......
using System; using System.Net;
using System.Net;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
...@@ -13,6 +12,17 @@ namespace Titanium.Web.Proxy.Models ...@@ -13,6 +12,17 @@ namespace Titanium.Web.Proxy.Models
/// </summary> /// </summary>
public class ExplicitProxyEndPoint : ProxyEndPoint public class ExplicitProxyEndPoint : ProxyEndPoint
{ {
/// <summary>
/// Constructor.
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <param name="decryptSsl"></param>
public ExplicitProxyEndPoint(IPAddress ipAddress, int port, bool decryptSsl = true) : base(ipAddress, port,
decryptSsl)
{
}
internal bool IsSystemHttpProxy { get; set; } internal bool IsSystemHttpProxy { get; set; }
internal bool IsSystemHttpsProxy { get; set; } internal bool IsSystemHttpsProxy { get; set; }
...@@ -25,7 +35,8 @@ namespace Titanium.Web.Proxy.Models ...@@ -25,7 +35,8 @@ namespace Titanium.Web.Proxy.Models
/// <summary> /// <summary>
/// Intercept tunnel connect request /// Intercept tunnel connect request
/// Valid only for explicit endpoints /// Valid only for explicit endpoints
/// Set the <see cref="TunnelConnectSessionEventArgs.DecryptSsl"/> property to false if this HTTP connect request should'nt be decrypted and instead be relayed /// Set the <see cref="TunnelConnectSessionEventArgs.DecryptSsl" /> property to false if this HTTP connect request
/// should'nt be decrypted and instead be relayed
/// </summary> /// </summary>
public event AsyncEventHandler<TunnelConnectSessionEventArgs> BeforeTunnelConnectRequest; public event AsyncEventHandler<TunnelConnectSessionEventArgs> BeforeTunnelConnectRequest;
...@@ -35,17 +46,8 @@ namespace Titanium.Web.Proxy.Models ...@@ -35,17 +46,8 @@ namespace Titanium.Web.Proxy.Models
/// </summary> /// </summary>
public event AsyncEventHandler<TunnelConnectSessionEventArgs> BeforeTunnelConnectResponse; public event AsyncEventHandler<TunnelConnectSessionEventArgs> BeforeTunnelConnectResponse;
/// <summary> internal async Task InvokeBeforeTunnelConnectRequest(ProxyServer proxyServer,
/// Constructor. TunnelConnectSessionEventArgs connectArgs, ExceptionHandler exceptionFunc)
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <param name="decryptSsl"></param>
public ExplicitProxyEndPoint(IPAddress ipAddress, int port, bool decryptSsl = true) : base(ipAddress, port, decryptSsl)
{
}
internal async Task InvokeBeforeTunnelConnectRequest(ProxyServer proxyServer, TunnelConnectSessionEventArgs connectArgs, ExceptionHandler exceptionFunc)
{ {
if (BeforeTunnelConnectRequest != null) if (BeforeTunnelConnectRequest != null)
{ {
...@@ -53,7 +55,8 @@ namespace Titanium.Web.Proxy.Models ...@@ -53,7 +55,8 @@ namespace Titanium.Web.Proxy.Models
} }
} }
internal async Task InvokeBeforeTunnectConnectResponse(ProxyServer proxyServer, TunnelConnectSessionEventArgs connectArgs, ExceptionHandler exceptionFunc, bool isClientHello = false) internal async Task InvokeBeforeTunnectConnectResponse(ProxyServer proxyServer,
TunnelConnectSessionEventArgs connectArgs, ExceptionHandler exceptionFunc, bool isClientHello = false)
{ {
if (BeforeTunnelConnectResponse != null) if (BeforeTunnelConnectResponse != null)
{ {
......
...@@ -8,11 +8,13 @@ namespace Titanium.Web.Proxy.Models ...@@ -8,11 +8,13 @@ namespace Titanium.Web.Proxy.Models
/// </summary> /// </summary>
public class ExternalProxy public class ExternalProxy
{ {
private static readonly Lazy<NetworkCredential> defaultCredentials = new Lazy<NetworkCredential>(() => CredentialCache.DefaultNetworkCredentials); private static readonly Lazy<NetworkCredential> defaultCredentials =
new Lazy<NetworkCredential>(() => CredentialCache.DefaultNetworkCredentials);
private string userName;
private string password; private string password;
private string userName;
/// <summary> /// <summary>
/// Use default windows credentials? /// Use default windows credentials?
/// </summary> /// </summary>
......
using System; using System;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
...@@ -17,7 +18,9 @@ namespace Titanium.Web.Proxy.Models ...@@ -17,7 +18,9 @@ namespace Titanium.Web.Proxy.Models
internal static readonly Version Version11 = new Version(1, 1); internal static readonly Version Version11 = new Version(1, 1);
internal static HttpHeader ProxyConnectionKeepAlive = new HttpHeader("Proxy-Connection", "keep-alive"); internal static readonly Version Version20 = new Version(2, 0);
internal static readonly HttpHeader ProxyConnectionKeepAlive = new HttpHeader("Proxy-Connection", "keep-alive");
/// <summary> /// <summary>
/// Constructor. /// Constructor.
...@@ -62,11 +65,11 @@ namespace Titanium.Web.Proxy.Models ...@@ -62,11 +65,11 @@ namespace Titanium.Web.Proxy.Models
return result; return result;
} }
internal async Task WriteToStreamAsync(HttpWriter writer) internal async Task WriteToStreamAsync(HttpWriter writer, CancellationToken cancellationToken)
{ {
await writer.WriteAsync(Name); await writer.WriteAsync(Name, cancellationToken);
await writer.WriteAsync(": "); await writer.WriteAsync(": ", cancellationToken);
await writer.WriteLineAsync(Value); await writer.WriteLineAsync(Value, cancellationToken);
} }
} }
} }
using System.Collections.Generic; using System.Net;
using System.Linq;
using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text.RegularExpressions;
namespace Titanium.Web.Proxy.Models namespace Titanium.Web.Proxy.Models
{ {
......
using System.Net; using System.Net;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy.Models namespace Titanium.Web.Proxy.Models
{ {
...@@ -8,6 +11,18 @@ namespace Titanium.Web.Proxy.Models ...@@ -8,6 +11,18 @@ namespace Titanium.Web.Proxy.Models
/// </summary> /// </summary>
public class TransparentProxyEndPoint : ProxyEndPoint public class TransparentProxyEndPoint : ProxyEndPoint
{ {
/// <summary>
/// Constructor.
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <param name="decryptSsl"></param>
public TransparentProxyEndPoint(IPAddress ipAddress, int port, bool decryptSsl = true) : base(ipAddress, port,
decryptSsl)
{
GenericCertificateName = "localhost";
}
/// <summary> /// <summary>
/// Name of the Certificate need to be sent (same as the hostname we want to proxy) /// Name of the Certificate need to be sent (same as the hostname we want to proxy)
/// This is valid only when UseServerNameIndication is set to false /// This is valid only when UseServerNameIndication is set to false
...@@ -15,14 +30,17 @@ namespace Titanium.Web.Proxy.Models ...@@ -15,14 +30,17 @@ namespace Titanium.Web.Proxy.Models
public string GenericCertificateName { get; set; } public string GenericCertificateName { get; set; }
/// <summary> /// <summary>
/// Constructor. /// Before Ssl authentication
/// </summary> /// </summary>
/// <param name="ipAddress"></param> public event AsyncEventHandler<BeforeSslAuthenticateEventArgs> BeforeSslAuthenticate;
/// <param name="port"></param>
/// <param name="decryptSsl"></param> internal async Task InvokeBeforeSslAuthenticate(ProxyServer proxyServer,
public TransparentProxyEndPoint(IPAddress ipAddress, int port, bool decryptSsl = true) : base(ipAddress, port, decryptSsl) BeforeSslAuthenticateEventArgs connectArgs, ExceptionHandler exceptionFunc)
{ {
GenericCertificateName = "localhost"; if (BeforeSslAuthenticate != null)
{
await BeforeSslAuthenticate.InvokeAsync(proxyServer, connectArgs, exceptionFunc);
}
} }
} }
} }
...@@ -8,6 +8,11 @@ namespace Titanium.Web.Proxy.Network ...@@ -8,6 +8,11 @@ namespace Titanium.Web.Proxy.Network
/// </summary> /// </summary>
internal class CachedCertificate internal class CachedCertificate
{ {
internal CachedCertificate()
{
LastAccess = DateTime.Now;
}
internal X509Certificate2 Certificate { get; set; } internal X509Certificate2 Certificate { get; set; }
/// <summary> /// <summary>
...@@ -15,10 +20,5 @@ namespace Titanium.Web.Proxy.Network ...@@ -15,10 +20,5 @@ namespace Titanium.Web.Proxy.Network
/// Usefull in determining its cache lifetime /// Usefull in determining its cache lifetime
/// </summary> /// </summary>
internal DateTime LastAccess { get; set; } internal DateTime LastAccess { get; set; }
internal CachedCertificate()
{
LastAccess = DateTime.Now;
}
} }
} }
using System; using System;
using System.IO; using System.IO;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Pkcs; using Org.BouncyCastle.Asn1.Pkcs;
...@@ -18,6 +17,7 @@ using Org.BouncyCastle.Security; ...@@ -18,6 +17,7 @@ using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.X509; using Org.BouncyCastle.X509;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
using X509Certificate = Org.BouncyCastle.X509.X509Certificate;
namespace Titanium.Web.Proxy.Network.Certificate namespace Titanium.Web.Proxy.Network.Certificate
{ {
...@@ -80,7 +80,8 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -80,7 +80,8 @@ namespace Titanium.Web.Proxy.Network.Certificate
var certificateGenerator = new X509V3CertificateGenerator(); var certificateGenerator = new X509V3CertificateGenerator();
// Serial Number // Serial Number
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), secureRandom); var serialNumber =
BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), secureRandom);
certificateGenerator.SetSerialNumber(serialNumber); certificateGenerator.SetSerialNumber(serialNumber);
// Issuer and Subject Name // Issuer and Subject Name
...@@ -95,10 +96,11 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -95,10 +96,11 @@ namespace Titanium.Web.Proxy.Network.Certificate
if (hostName != null) if (hostName != null)
{ {
//add subject alternative names //add subject alternative names
var subjectAlternativeNames = new Asn1Encodable[] { new GeneralName(GeneralName.DnsName, hostName), }; var subjectAlternativeNames = new Asn1Encodable[] { new GeneralName(GeneralName.DnsName, hostName) };
var subjectAlternativeNamesExtension = new DerSequence(subjectAlternativeNames); var subjectAlternativeNamesExtension = new DerSequence(subjectAlternativeNames);
certificateGenerator.AddExtension(X509Extensions.SubjectAlternativeName.Id, false, subjectAlternativeNamesExtension); certificateGenerator.AddExtension(X509Extensions.SubjectAlternativeName.Id, false,
subjectAlternativeNamesExtension);
} }
// Subject Public Key // Subject Public Key
...@@ -110,13 +112,15 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -110,13 +112,15 @@ namespace Titanium.Web.Proxy.Network.Certificate
certificateGenerator.SetPublicKey(subjectKeyPair.Public); certificateGenerator.SetPublicKey(subjectKeyPair.Public);
// Set certificate intended purposes to only Server Authentication // Set certificate intended purposes to only Server Authentication
certificateGenerator.AddExtension(X509Extensions.ExtendedKeyUsage.Id, false, new ExtendedKeyUsage(KeyPurposeID.IdKPServerAuth)); certificateGenerator.AddExtension(X509Extensions.ExtendedKeyUsage.Id, false,
new ExtendedKeyUsage(KeyPurposeID.IdKPServerAuth));
if (issuerPrivateKey == null) if (issuerPrivateKey == null)
{ {
certificateGenerator.AddExtension(X509Extensions.BasicConstraints.Id, true, new BasicConstraints(true)); certificateGenerator.AddExtension(X509Extensions.BasicConstraints.Id, true, new BasicConstraints(true));
} }
var signatureFactory = new Asn1SignatureFactory(signatureAlgorithm, issuerPrivateKey ?? subjectKeyPair.Private, secureRandom); var signatureFactory = new Asn1SignatureFactory(signatureAlgorithm,
issuerPrivateKey ?? subjectKeyPair.Private, secureRandom);
// Self-sign the certificate // Self-sign the certificate
var certificate = certificateGenerator.Generate(signatureFactory); var certificate = certificateGenerator.Generate(signatureFactory);
...@@ -132,7 +136,8 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -132,7 +136,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, var rsaparams = new RsaPrivateCrtKeyParameters(rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent,
rsa.Prime1, rsa.Prime2, rsa.Exponent1,
rsa.Exponent2, rsa.Coefficient); rsa.Exponent2, rsa.Coefficient);
#if NET45 #if NET45
...@@ -159,7 +164,7 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -159,7 +164,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
return x509Certificate; return x509Certificate;
} }
private static X509Certificate2 WithPrivateKey(Org.BouncyCastle.X509.X509Certificate certificate, AsymmetricKeyParameter privateKey) private static X509Certificate2 WithPrivateKey(X509Certificate certificate, AsymmetricKeyParameter privateKey)
{ {
const string password = "password"; const string password = "password";
var store = new Pkcs12Store(); var store = new Pkcs12Store();
...@@ -185,25 +190,29 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -185,25 +190,29 @@ namespace Titanium.Web.Proxy.Network.Certificate
/// <param name="validTo">The valid to.</param> /// <param name="validTo">The valid to.</param>
/// <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, string hostName, string subjectName,
DateTime validFrom, DateTime validTo, X509Certificate2 signingCertificate) DateTime validFrom, DateTime validTo, X509Certificate2 signingCertificate)
{ {
if (isRoot != (null == 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)); throw new ArgumentException(
"You must specify a Signing Certificate if and only if you are not creating a root.",
nameof(signingCertificate));
} }
if (isRoot) if (isRoot)
{ {
return GenerateCertificate(null, subjectName, subjectName, validFrom, validTo); return GenerateCertificate(null, subjectName, subjectName, validFrom, validTo);
} }
else
{
var kp = DotNetUtilities.GetKeyPair(signingCertificate.PrivateKey); var kp = DotNetUtilities.GetKeyPair(signingCertificate.PrivateKey);
return GenerateCertificate(hostName, subjectName, signingCertificate.Subject, validFrom, validTo, issuerPrivateKey: kp.Private); return GenerateCertificate(hostName, subjectName, signingCertificate.Subject, validFrom, validTo,
} issuerPrivateKey: kp.Private);
} }
/// <summary> /// <summary>
...@@ -217,7 +226,7 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -217,7 +226,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
/// <returns>X509Certificate2.</returns> /// <returns>X509Certificate2.</returns>
private X509Certificate2 MakeCertificateInternal(string subject, bool isRoot, private X509Certificate2 MakeCertificateInternal(string subject, bool isRoot,
bool switchToMtaIfNeeded, X509Certificate2 signingCert = null, bool switchToMtaIfNeeded, X509Certificate2 signingCert = null,
CancellationToken cancellationToken = default(CancellationToken)) CancellationToken cancellationToken = default)
{ {
#if NET45 #if NET45
if (switchToMtaIfNeeded && Thread.CurrentThread.GetApartmentState() != ApartmentState.MTA) if (switchToMtaIfNeeded && Thread.CurrentThread.GetApartmentState() != ApartmentState.MTA)
...@@ -249,7 +258,9 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -249,7 +258,9 @@ namespace Titanium.Web.Proxy.Network.Certificate
} }
#endif #endif
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);
} }
} }
} }
...@@ -11,40 +11,39 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -11,40 +11,39 @@ namespace Titanium.Web.Proxy.Network.Certificate
/// </summary> /// </summary>
internal class WinCertificateMaker : ICertificateMaker internal class WinCertificateMaker : ICertificateMaker
{ {
private readonly Type typeX500DN; private readonly ExceptionHandler exceptionFunc;
private readonly Type typeX509PrivateKey; private readonly string sProviderName = "Microsoft Enhanced Cryptographic Provider v1.0";
private readonly Type typeOID; private readonly Type typeAltNamesCollection;
private readonly Type typeOIDS; private readonly Type typeBasicConstraints;
private readonly Type typeKUExt; private readonly Type typeCAlternativeName;
private readonly Type typeEKUExt; private readonly Type typeEKUExt;
private readonly Type typeRequestCert; private readonly Type typeExtNames;
private readonly Type typeX509Extensions; private readonly Type typeKUExt;
private readonly Type typeBasicConstraints; private readonly Type typeOID;
private readonly Type typeSignerCertificate; private readonly Type typeOIDS;
private readonly Type typeX509Enrollment; private readonly Type typeRequestCert;
private readonly Type typeAltNamesCollection; private readonly Type typeSignerCertificate;
private readonly Type typeX500DN;
private readonly Type typeExtNames; private readonly Type typeX509Enrollment;
private readonly Type typeCAlternativeName; private readonly Type typeX509Extensions;
private readonly string sProviderName = "Microsoft Enhanced Cryptographic Provider v1.0"; private readonly Type typeX509PrivateKey;
private object sharedPrivateKey; private object sharedPrivateKey;
private readonly ExceptionHandler exceptionFunc;
/// <summary> /// <summary>
/// Constructor. /// Constructor.
/// </summary> /// </summary>
...@@ -88,7 +87,9 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -88,7 +87,9 @@ namespace Titanium.Web.Proxy.Network.Certificate
{ {
if (isRoot != (null == 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(isRoot)); throw new ArgumentException(
"You must specify a Signing Certificate if and only if you are not creating a root.",
nameof(isRoot));
} }
var x500CertDN = Activator.CreateInstance(typeX500DN); var x500CertDN = Activator.CreateInstance(typeX500DN);
...@@ -114,20 +115,25 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -114,20 +115,25 @@ namespace Titanium.Web.Proxy.Network.Certificate
{ {
sharedPrivateKey = Activator.CreateInstance(typeX509PrivateKey); sharedPrivateKey = Activator.CreateInstance(typeX509PrivateKey);
typeValue = new object[] { sProviderName }; typeValue = new object[] { sProviderName };
typeX509PrivateKey.InvokeMember("ProviderName", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue); typeX509PrivateKey.InvokeMember("ProviderName", BindingFlags.PutDispProperty, null, sharedPrivateKey,
typeValue);
typeValue[0] = 2; typeValue[0] = 2;
typeX509PrivateKey.InvokeMember("ExportPolicy", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue); typeX509PrivateKey.InvokeMember("ExportPolicy", BindingFlags.PutDispProperty, null, sharedPrivateKey,
typeValue);
typeValue = new object[] { isRoot ? 2 : 1 }; typeValue = new object[] { isRoot ? 2 : 1 };
typeX509PrivateKey.InvokeMember("KeySpec", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue); typeX509PrivateKey.InvokeMember("KeySpec", BindingFlags.PutDispProperty, null, sharedPrivateKey,
typeValue);
if (!isRoot) if (!isRoot)
{ {
typeValue = new object[] { 176 }; typeValue = new object[] { 176 };
typeX509PrivateKey.InvokeMember("KeyUsage", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue); typeX509PrivateKey.InvokeMember("KeyUsage", BindingFlags.PutDispProperty, null, sharedPrivateKey,
typeValue);
} }
typeValue[0] = privateKeyLength; typeValue[0] = privateKeyLength;
typeX509PrivateKey.InvokeMember("Length", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue); typeX509PrivateKey.InvokeMember("Length", BindingFlags.PutDispProperty, null, sharedPrivateKey,
typeValue);
typeX509PrivateKey.InvokeMember("Create", BindingFlags.InvokeMethod, null, sharedPrivateKey, null); typeX509PrivateKey.InvokeMember("Create", BindingFlags.InvokeMethod, null, sharedPrivateKey, null);
if (!isRoot) if (!isRoot)
...@@ -153,7 +159,8 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -153,7 +159,8 @@ namespace Titanium.Web.Proxy.Network.Certificate
var requestCert = Activator.CreateInstance(typeRequestCert); var requestCert = Activator.CreateInstance(typeRequestCert);
typeValue = new[] { 1, sharedPrivateKey, string.Empty }; typeValue = new[] { 1, sharedPrivateKey, string.Empty };
typeRequestCert.InvokeMember("InitializeFromPrivateKey", BindingFlags.InvokeMethod, null, requestCert, typeValue); typeRequestCert.InvokeMember("InitializeFromPrivateKey", BindingFlags.InvokeMethod, null, requestCert,
typeValue);
typeValue = new[] { x500CertDN }; typeValue = new[] { x500CertDN };
typeRequestCert.InvokeMember("Subject", BindingFlags.PutDispProperty, null, requestCert, typeValue); typeRequestCert.InvokeMember("Subject", BindingFlags.PutDispProperty, null, requestCert, typeValue);
typeValue[0] = x500RootCertDN; typeValue[0] = x500RootCertDN;
...@@ -168,7 +175,8 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -168,7 +175,8 @@ namespace Titanium.Web.Proxy.Network.Certificate
typeValue[0] = 176; typeValue[0] = 176;
typeKUExt.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, kuExt, typeValue); typeKUExt.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, kuExt, typeValue);
var certificate = typeRequestCert.InvokeMember("X509Extensions", BindingFlags.GetProperty, null, requestCert, null); var certificate =
typeRequestCert.InvokeMember("X509Extensions", BindingFlags.GetProperty, null, requestCert, null);
typeValue = new object[1]; typeValue = new object[1];
if (!isRoot) if (!isRoot)
...@@ -190,10 +198,12 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -190,10 +198,12 @@ namespace Titanium.Web.Proxy.Network.Certificate
var altDnsNames = Activator.CreateInstance(typeCAlternativeName); var altDnsNames = Activator.CreateInstance(typeCAlternativeName);
typeValue = new object[] { 3, subject }; typeValue = new object[] { 3, subject };
typeCAlternativeName.InvokeMember("InitializeFromString", BindingFlags.InvokeMethod, null, altDnsNames, typeValue); typeCAlternativeName.InvokeMember("InitializeFromString", BindingFlags.InvokeMethod, null, altDnsNames,
typeValue);
typeValue = new[] { altDnsNames }; typeValue = new[] { altDnsNames };
typeAltNamesCollection.InvokeMember("Add", BindingFlags.InvokeMethod, null, altNameCollection, typeValue); typeAltNamesCollection.InvokeMember("Add", BindingFlags.InvokeMethod, null, altNameCollection,
typeValue);
typeValue = new[] { altNameCollection }; typeValue = new[] { altNameCollection };
...@@ -208,16 +218,19 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -208,16 +218,19 @@ namespace Titanium.Web.Proxy.Network.Certificate
var signerCertificate = Activator.CreateInstance(typeSignerCertificate); var signerCertificate = Activator.CreateInstance(typeSignerCertificate);
typeValue = new object[] { 0, 0, 12, signingCertificate.Thumbprint }; typeValue = new object[] { 0, 0, 12, signingCertificate.Thumbprint };
typeSignerCertificate.InvokeMember("Initialize", BindingFlags.InvokeMethod, null, signerCertificate, typeValue); typeSignerCertificate.InvokeMember("Initialize", BindingFlags.InvokeMethod, null, signerCertificate,
typeValue);
typeValue = new[] { signerCertificate }; typeValue = new[] { signerCertificate };
typeRequestCert.InvokeMember("SignerCertificate", BindingFlags.PutDispProperty, null, requestCert, typeValue); typeRequestCert.InvokeMember("SignerCertificate", BindingFlags.PutDispProperty, null, requestCert,
typeValue);
} }
else else
{ {
var basicConstraints = Activator.CreateInstance(typeBasicConstraints); var basicConstraints = Activator.CreateInstance(typeBasicConstraints);
typeValue = new object[] { "true", "0" }; typeValue = new object[] { "true", "0" };
typeBasicConstraints.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, basicConstraints, typeValue); typeBasicConstraints.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, basicConstraints,
typeValue);
typeValue = new[] { basicConstraints }; typeValue = new[] { basicConstraints };
typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, certificate, typeValue); typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, certificate, typeValue);
} }
...@@ -234,29 +247,34 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -234,29 +247,34 @@ namespace Titanium.Web.Proxy.Network.Certificate
var x509Enrollment = Activator.CreateInstance(typeX509Enrollment); var x509Enrollment = Activator.CreateInstance(typeX509Enrollment);
typeValue[0] = requestCert; typeValue[0] = requestCert;
typeX509Enrollment.InvokeMember("InitializeFromRequest", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue); typeX509Enrollment.InvokeMember("InitializeFromRequest", BindingFlags.InvokeMethod, null, x509Enrollment,
typeValue);
if (isRoot) if (isRoot)
{ {
typeValue[0] = fullSubject; typeValue[0] = fullSubject;
typeX509Enrollment.InvokeMember("CertificateFriendlyName", BindingFlags.PutDispProperty, null, x509Enrollment, typeValue); typeX509Enrollment.InvokeMember("CertificateFriendlyName", BindingFlags.PutDispProperty, null,
x509Enrollment, typeValue);
} }
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);
typeValue = new[] { 2, createCertRequest, 0, string.Empty }; typeValue = new[] { 2, createCertRequest, 0, string.Empty };
typeX509Enrollment.InvokeMember("InstallResponse", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue); typeX509Enrollment.InvokeMember("InstallResponse", BindingFlags.InvokeMethod, null, x509Enrollment,
typeValue);
typeValue = new object[] { null, 0, 1 }; typeValue = new object[] { null, 0, 1 };
string 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);
} }
private X509Certificate2 MakeCertificateInternal(string sSubjectCN, bool isRoot, private X509Certificate2 MakeCertificateInternal(string sSubjectCN, bool isRoot,
bool switchToMTAIfNeeded, X509Certificate2 signingCert = null, bool switchToMTAIfNeeded, X509Certificate2 signingCert = null,
CancellationToken cancellationToken = default(CancellationToken)) CancellationToken cancellationToken = default)
{ {
X509Certificate2 certificate = null; X509Certificate2 certificate = null;
if (switchToMTAIfNeeded && Thread.CurrentThread.GetApartmentState() != ApartmentState.MTA) if (switchToMTAIfNeeded && Thread.CurrentThread.GetApartmentState() != ApartmentState.MTA)
...@@ -299,7 +317,8 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -299,7 +317,8 @@ namespace Titanium.Web.Proxy.Network.Certificate
var graceTime = DateTime.Now.AddDays(GraceDays); var graceTime = DateTime.Now.AddDays(GraceDays);
var now = DateTime.Now; var now = DateTime.Now;
certificate = MakeCertificate(isRoot, sSubjectCN, fullSubject, keyLength, HashAlgo, graceTime, now.AddDays(ValidDays), isRoot ? null : signingCert); certificate = MakeCertificate(isRoot, sSubjectCN, fullSubject, keyLength, HashAlgo, graceTime,
now.AddDays(ValidDays), isRoot ? null : signingCert);
return certificate; return certificate;
} }
} }
......
...@@ -34,32 +34,82 @@ namespace Titanium.Web.Proxy.Network ...@@ -34,32 +34,82 @@ namespace Titanium.Web.Proxy.Network
/// </summary> /// </summary>
public sealed class CertificateManager : IDisposable public sealed class CertificateManager : IDisposable
{ {
private const string defaultRootCertificateIssuer = "Titanium"; private const string defaultRootCertificateIssuer = "Titanium";
private const string defaultRootRootCertificateName = "Titanium Root Certificate Authority"; private const string defaultRootRootCertificateName = "Titanium Root Certificate Authority";
private CertificateEngine engine; /// <summary>
/// Cache dictionary
/// </summary>
private readonly ConcurrentDictionary<string, CachedCertificate> certificateCache;
private readonly ExceptionHandler exceptionFunc;
private readonly ConcurrentDictionary<string, Task<X509Certificate2>> pendingCertificateCreationTasks;
private ICertificateMaker certEngine; private ICertificateMaker certEngine;
private CertificateEngine engine;
private string issuer; private string issuer;
private string rootCertificateName; private bool pfxFileExists;
private bool pfxFileExists = false; private X509Certificate2 rootCertificate;
private bool clearCertificates { get; set; } private string rootCertificateName;
private X509Certificate2 rootCertificate;
/// <summary> /// <summary>
/// Cache dictionary /// Constructor.
/// </summary> /// </summary>
private readonly ConcurrentDictionary<string, CachedCertificate> certificateCache; /// <param name="rootCertificateName">Name of root certificate.</param>
private readonly ConcurrentDictionary<string, Task<X509Certificate2>> pendingCertificateCreationTasks; /// <param name="rootCertificateIssuerName">Name of root certificate issuer.</param>
/// <param name="userTrustRootCertificate"></param>
/// <param name="machineTrustRootCertificate">
/// Note:setting machineTrustRootCertificate to true will force
/// userTrustRootCertificate to true
/// </param>
/// <param name="trustRootCertificateAsAdmin"></param>
/// <param name="exceptionFunc"></param>
internal CertificateManager(string rootCertificateName, string rootCertificateIssuerName,
bool userTrustRootCertificate, bool machineTrustRootCertificate, bool trustRootCertificateAsAdmin,
ExceptionHandler exceptionFunc)
{
this.exceptionFunc = exceptionFunc;
private readonly ExceptionHandler exceptionFunc; UserTrustRoot = userTrustRootCertificate;
if (machineTrustRootCertificate)
{
userTrustRootCertificate = true;
}
MachineTrustRoot = machineTrustRootCertificate;
TrustRootAsAdministrator = trustRootCertificateAsAdmin;
if (rootCertificateName != null)
{
RootCertificateName = rootCertificateName;
}
if (rootCertificateIssuerName != null)
{
RootCertificateIssuerName = rootCertificateIssuerName;
}
if (RunTime.IsWindows)
{
CertificateEngine = CertificateEngine.DefaultWindows;
}
else
{
CertificateEngine = CertificateEngine.BouncyCastle;
}
certificateCache = new ConcurrentDictionary<string, CachedCertificate>();
pendingCertificateCreationTasks = new ConcurrentDictionary<string, Task<X509Certificate2>>();
}
private bool clearCertificates { get; set; }
/// <summary> /// <summary>
/// Is the root certificate used by this proxy is valid? /// Is the root certificate used by this proxy is valid?
...@@ -69,19 +119,19 @@ namespace Titanium.Web.Proxy.Network ...@@ -69,19 +119,19 @@ namespace Titanium.Web.Proxy.Network
/// <summary> /// <summary>
/// Trust the RootCertificate used by this proxy server for current user /// Trust the RootCertificate used by this proxy server for current user
/// </summary> /// </summary>
internal bool UserTrustRoot { get; set; } = false; internal bool UserTrustRoot { get; set; }
/// <summary> /// <summary>
/// Trust the RootCertificate used by this proxy server for current machine /// Trust the RootCertificate used by this proxy server for current machine
/// Needs elevated permission, otherwise will fail silently. /// Needs elevated permission, otherwise will fail silently.
/// </summary> /// </summary>
internal bool MachineTrustRoot { get; set; } = false; internal bool MachineTrustRoot { get; set; }
/// <summary> /// <summary>
/// Whether trust operations should be done with elevated privillages /// Whether trust operations should be done with elevated privillages
/// Will prompt with UAC if required. Works only on Windows. /// Will prompt with UAC if required. Works only on Windows.
/// </summary> /// </summary>
internal bool TrustRootAsAdministrator { get; set; } = false; internal bool TrustRootAsAdministrator { get; set; }
/// <summary> /// <summary>
/// Select Certificate Engine /// Select Certificate Engine
...@@ -122,7 +172,10 @@ namespace Titanium.Web.Proxy.Network ...@@ -122,7 +172,10 @@ namespace Titanium.Web.Proxy.Network
/// <summary> /// <summary>
/// Name(path) of the Root certificate file /// Name(path) of the Root certificate file
/// <para>Set the name(path) of the .pfx file. If it is string.Empty Root certificate file will be named as "rootCert.pfx" (and will be saved in proxy dll directory)</para> /// <para>
/// Set the name(path) of the .pfx file. If it is string.Empty Root certificate file will be named as
/// "rootCert.pfx" (and will be saved in proxy dll directory)
/// </para>
/// </summary> /// </summary>
public string PfxFilePath { get; set; } = string.Empty; public string PfxFilePath { get; set; } = string.Empty;
...@@ -133,10 +186,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -133,10 +186,7 @@ namespace Titanium.Web.Proxy.Network
public string RootCertificateIssuerName public string RootCertificateIssuerName
{ {
get => issuer ?? defaultRootCertificateIssuer; get => issuer ?? defaultRootCertificateIssuer;
set set => issuer = value;
{
issuer = value;
}
} }
/// <summary> /// <summary>
...@@ -149,10 +199,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -149,10 +199,7 @@ namespace Titanium.Web.Proxy.Network
public string RootCertificateName public string RootCertificateName
{ {
get => rootCertificateName ?? defaultRootRootCertificateName; get => rootCertificateName ?? defaultRootRootCertificateName;
set set => rootCertificateName = value;
{
rootCertificateName = value;
}
} }
/// <summary> /// <summary>
...@@ -190,51 +237,11 @@ namespace Titanium.Web.Proxy.Network ...@@ -190,51 +237,11 @@ namespace Titanium.Web.Proxy.Network
/// </summary> /// </summary>
public X509KeyStorageFlags StorageFlag { get; set; } = X509KeyStorageFlags.Exportable; public X509KeyStorageFlags StorageFlag { get; set; } = X509KeyStorageFlags.Exportable;
/// <summary> /// <summary>
/// Constructor. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary> /// </summary>
/// <param name="rootCertificateName">Name of root certificate.</param> public void Dispose()
/// <param name="rootCertificateIssuerName">Name of root certificate issuer.</param>
/// <param name="userTrustRootCertificate"></param>
/// <param name="machineTrustRootCertificate">Note:setting machineTrustRootCertificate to true will force userTrustRootCertificate to true</param>
/// <param name="trustRootCertificateAsAdmin"></param>
/// <param name="exceptionFunc"></param>
internal CertificateManager(string rootCertificateName, string rootCertificateIssuerName, bool userTrustRootCertificate, bool machineTrustRootCertificate, bool trustRootCertificateAsAdmin, ExceptionHandler exceptionFunc)
{
this.exceptionFunc = exceptionFunc;
UserTrustRoot = userTrustRootCertificate;
if (machineTrustRootCertificate)
{
userTrustRootCertificate = true;
}
MachineTrustRoot = machineTrustRootCertificate;
TrustRootAsAdministrator = trustRootCertificateAsAdmin;
if (rootCertificateName != null)
{ {
RootCertificateName = rootCertificateName;
}
if (rootCertificateIssuerName != null)
{
RootCertificateIssuerName = rootCertificateIssuerName;
}
if (RunTime.IsWindows)
{
//this is faster in Windows based on tests (see unit test project CertificateManagerTests.cs)
CertificateEngine = CertificateEngine.DefaultWindows;
}
else
{
CertificateEngine = CertificateEngine.BouncyCastle;
}
certificateCache = new ConcurrentDictionary<string, CachedCertificate>();
pendingCertificateCreationTasks = new ConcurrentDictionary<string, Task<X509Certificate2>>();
} }
...@@ -250,7 +257,9 @@ namespace Titanium.Web.Proxy.Network ...@@ -250,7 +257,9 @@ namespace Titanium.Web.Proxy.Network
string path = Path.GetDirectoryName(assemblyLocation); string path = Path.GetDirectoryName(assemblyLocation);
if (null == path) if (null == path)
{
throw new NullReferenceException(); throw new NullReferenceException();
}
return path; return path;
} }
...@@ -295,7 +304,8 @@ namespace Titanium.Web.Proxy.Network ...@@ -295,7 +304,8 @@ namespace Titanium.Web.Proxy.Network
|| FindCertificates(StoreName.My, storeLocation, value).Count > 0); || FindCertificates(StoreName.My, storeLocation, value).Count > 0);
} }
private X509Certificate2Collection FindCertificates(StoreName storeName, StoreLocation storeLocation, string findValue) private X509Certificate2Collection FindCertificates(StoreName storeName, StoreLocation storeLocation,
string findValue)
{ {
var x509Store = new X509Store(storeName, storeLocation); var x509Store = new X509Store(storeName, storeLocation);
try try
...@@ -334,13 +344,13 @@ namespace Titanium.Web.Proxy.Network ...@@ -334,13 +344,13 @@ namespace Titanium.Web.Proxy.Network
{ {
x509store.Open(OpenFlags.ReadWrite); x509store.Open(OpenFlags.ReadWrite);
x509store.Add(RootCertificate); x509store.Add(RootCertificate);
} }
catch (Exception e) catch (Exception e)
{ {
exceptionFunc( exceptionFunc(
new Exception("Failed to make system trust root certificate " new Exception("Failed to make system trust root certificate "
+ $" for {storeName}\\{storeLocation} store location. You may need admin rights.", e)); + $" for {storeName}\\{storeLocation} store location. You may need admin rights.",
e));
} }
finally finally
{ {
...@@ -355,7 +365,8 @@ namespace Titanium.Web.Proxy.Network ...@@ -355,7 +365,8 @@ namespace Titanium.Web.Proxy.Network
/// <param name="storeLocation"></param> /// <param name="storeLocation"></param>
/// <param name="certificate"></param> /// <param name="certificate"></param>
/// <returns></returns> /// <returns></returns>
private void UninstallCertificate(StoreName storeName, StoreLocation storeLocation, X509Certificate2 certificate) private void UninstallCertificate(StoreName storeName, StoreLocation storeLocation,
X509Certificate2 certificate)
{ {
if (certificate == null) if (certificate == null)
{ {
...@@ -397,7 +408,6 @@ namespace Titanium.Web.Proxy.Network ...@@ -397,7 +408,6 @@ namespace Titanium.Web.Proxy.Network
if (CertificateEngine == CertificateEngine.DefaultWindows) if (CertificateEngine == CertificateEngine.DefaultWindows)
{ {
//prevent certificates getting piled up in User\Personal store
Task.Run(() => UninstallCertificate(StoreName.My, StoreLocation.CurrentUser, certificate)); Task.Run(() => UninstallCertificate(StoreName.My, StoreLocation.CurrentUser, certificate));
} }
...@@ -412,7 +422,6 @@ namespace Titanium.Web.Proxy.Network ...@@ -412,7 +422,6 @@ namespace Titanium.Web.Proxy.Network
/// <returns></returns> /// <returns></returns>
internal X509Certificate2 CreateCertificate(string certificateName, bool isRootCertificate) internal X509Certificate2 CreateCertificate(string certificateName, bool isRootCertificate)
{ {
X509Certificate2 certificate = null; X509Certificate2 certificate = null;
try try
{ {
...@@ -421,7 +430,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -421,7 +430,7 @@ namespace Titanium.Web.Proxy.Network
string path = GetCertificatePath(); string path = GetCertificatePath();
string subjectName = ProxyConstants.CNRemoverRegex.Replace(certificateName, string.Empty); string subjectName = ProxyConstants.CNRemoverRegex.Replace(certificateName, string.Empty);
subjectName = subjectName.Replace("*", "$x$"); subjectName = subjectName.Replace("*", "$x$");
var certificatePath = Path.Combine(path, subjectName + ".pfx"); string certificatePath = Path.Combine(path, subjectName + ".pfx");
if (!File.Exists(certificatePath)) if (!File.Exists(certificatePath))
{ {
...@@ -434,7 +443,10 @@ namespace Titanium.Web.Proxy.Network ...@@ -434,7 +443,10 @@ namespace Titanium.Web.Proxy.Network
{ {
File.WriteAllBytes(certificatePath, certificate.Export(X509ContentType.Pkcs12)); File.WriteAllBytes(certificatePath, certificate.Export(X509ContentType.Pkcs12));
} }
catch (Exception e) { exceptionFunc(new Exception("Failed to save fake certificate.", e)); } catch (Exception e)
{
exceptionFunc(new Exception("Failed to save fake certificate.", e));
}
}); });
} }
else else
...@@ -483,8 +495,6 @@ namespace Titanium.Web.Proxy.Network ...@@ -483,8 +495,6 @@ namespace Titanium.Web.Proxy.Network
Task<X509Certificate2> task; Task<X509Certificate2> task;
if (pendingCertificateCreationTasks.TryGetValue(certificateName, out task)) if (pendingCertificateCreationTasks.TryGetValue(certificateName, out task))
{ {
//certificate already added to cache
//just return the result here
return await task; return await task;
} }
...@@ -494,14 +504,12 @@ namespace Titanium.Web.Proxy.Network ...@@ -494,14 +504,12 @@ namespace Titanium.Web.Proxy.Network
var result = CreateCertificate(certificateName, false); var result = CreateCertificate(certificateName, false);
if (result != null) if (result != null)
{ {
//this is ConcurrentDictionary
//if key exists it will silently handle; no need for locking
certificateCache.TryAdd(certificateName, new CachedCertificate certificateCache.TryAdd(certificateName, new CachedCertificate
{ {
Certificate = result Certificate = result
}); });
} }
return result; return result;
}); });
pendingCertificateCreationTasks.TryAdd(certificateName, task); pendingCertificateCreationTasks.TryAdd(certificateName, task);
...@@ -511,7 +519,6 @@ namespace Titanium.Web.Proxy.Network ...@@ -511,7 +519,6 @@ namespace Titanium.Web.Proxy.Network
pendingCertificateCreationTasks.TryRemove(certificateName, out task); pendingCertificateCreationTasks.TryRemove(certificateName, out task);
return certificate; return certificate;
} }
/// <summary> /// <summary>
...@@ -526,9 +533,10 @@ namespace Titanium.Web.Proxy.Network ...@@ -526,9 +533,10 @@ namespace Titanium.Web.Proxy.Network
var outdated = certificateCache.Where(x => x.Value.LastAccess < cutOff).ToList(); var outdated = certificateCache.Where(x => x.Value.LastAccess < cutOff).ToList();
CachedCertificate removed;
foreach (var cache in outdated) foreach (var cache in outdated)
certificateCache.TryRemove(cache.Key, out removed); {
certificateCache.TryRemove(cache.Key, out _);
}
//after a minute come back to check for outdated certificates in cache //after a minute come back to check for outdated certificates in cache
await Task.Delay(1000 * 60); await Task.Delay(1000 * 60);
...@@ -629,14 +637,18 @@ namespace Titanium.Web.Proxy.Network ...@@ -629,14 +637,18 @@ namespace Titanium.Web.Proxy.Network
/// <summary> /// <summary>
/// Manually load a Root certificate file from give path (.pfx file) /// Manually load a Root certificate file from give path (.pfx file)
/// </summary> /// </summary>
/// <param name="pfxFilePath">Set the name(path) of the .pfx file. If it is string.Empty Root certificate file will be named as "rootCert.pfx" (and will be saved in proxy dll directory)</param> /// <param name="pfxFilePath">
/// Set the name(path) of the .pfx file. If it is string.Empty Root certificate file will be
/// named as "rootCert.pfx" (and will be saved in proxy dll directory)
/// </param>
/// <param name="password">Set a password for the .pfx file</param> /// <param name="password">Set a password for the .pfx file</param>
/// <param name="overwritePfXFile">true : replace an existing .pfx file if password is incorect or if RootCertificate==null</param> /// <param name="overwritePfXFile">true : replace an existing .pfx file if password is incorect or if RootCertificate==null</param>
/// <param name="storageFlag"></param> /// <param name="storageFlag"></param>
/// <returns> /// <returns>
/// true if succeeded, else false /// true if succeeded, else false
/// </returns> /// </returns>
public bool LoadRootCertificate(string pfxFilePath, string password, bool overwritePfXFile = true, X509KeyStorageFlags storageFlag = X509KeyStorageFlags.Exportable) public bool LoadRootCertificate(string pfxFilePath, string password, bool overwritePfXFile = true,
X509KeyStorageFlags storageFlag = X509KeyStorageFlags.Exportable)
{ {
PfxFilePath = pfxFilePath; PfxFilePath = pfxFilePath;
PfxPassword = password; PfxPassword = password;
...@@ -645,7 +657,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -645,7 +657,7 @@ namespace Titanium.Web.Proxy.Network
RootCertificate = LoadRootCertificate(); RootCertificate = LoadRootCertificate();
return (RootCertificate != null); return RootCertificate != null;
} }
/// <summary> /// <summary>
...@@ -654,7 +666,6 @@ namespace Titanium.Web.Proxy.Network ...@@ -654,7 +666,6 @@ namespace Titanium.Web.Proxy.Network
/// </summary> /// </summary>
public void TrustRootCertificate(bool machineTrusted = false) public void TrustRootCertificate(bool machineTrusted = false)
{ {
//currentUser\personal //currentUser\personal
InstallCertificate(StoreName.My, StoreLocation.CurrentUser); InstallCertificate(StoreName.My, StoreLocation.CurrentUser);
...@@ -692,7 +703,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -692,7 +703,7 @@ namespace Titanium.Web.Proxy.Network
File.WriteAllBytes(pfxFileName, RootCertificate.Export(X509ContentType.Pkcs12, PfxPassword)); File.WriteAllBytes(pfxFileName, RootCertificate.Export(X509ContentType.Pkcs12, PfxPassword));
//currentUser\Root, currentMachine\Personal & currentMachine\Root //currentUser\Root, currentMachine\Personal & currentMachine\Root
var info = new ProcessStartInfo() var info = new ProcessStartInfo
{ {
FileName = "certutil.exe", FileName = "certutil.exe",
CreateNoWindow = true, CreateNoWindow = true,
...@@ -721,7 +732,6 @@ namespace Titanium.Web.Proxy.Network ...@@ -721,7 +732,6 @@ namespace Titanium.Web.Proxy.Network
process.WaitForExit(); process.WaitForExit();
File.Delete(pfxFileName); File.Delete(pfxFileName);
} }
catch (Exception e) catch (Exception e)
{ {
...@@ -751,7 +761,6 @@ namespace Titanium.Web.Proxy.Network ...@@ -751,7 +761,6 @@ namespace Titanium.Web.Proxy.Network
{ {
TrustRootCertificate(MachineTrustRoot); TrustRootCertificate(MachineTrustRoot);
} }
} }
/// <summary> /// <summary>
...@@ -759,7 +768,8 @@ namespace Titanium.Web.Proxy.Network ...@@ -759,7 +768,8 @@ namespace Titanium.Web.Proxy.Network
/// Also makes root certificate trusted based on provided parameters /// Also makes root certificate trusted based on provided parameters
/// Note:setting machineTrustRootCertificate to true will force userTrustRootCertificate to true /// Note:setting machineTrustRootCertificate to true will force userTrustRootCertificate to true
/// </summary> /// </summary>
public void EnsureRootCertificate(bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false, bool trustRootCertificateAsAdmin = false) public void EnsureRootCertificate(bool userTrustRootCertificate = true,
bool machineTrustRootCertificate = false, bool trustRootCertificateAsAdmin = false)
{ {
UserTrustRoot = userTrustRootCertificate; UserTrustRoot = userTrustRootCertificate;
if (machineTrustRootCertificate) if (machineTrustRootCertificate)
...@@ -812,12 +822,10 @@ namespace Titanium.Web.Proxy.Network ...@@ -812,12 +822,10 @@ namespace Titanium.Web.Proxy.Network
//this adds to both currentUser\Root & currentMachine\Root //this adds to both currentUser\Root & currentMachine\Root
UninstallCertificate(StoreName.Root, StoreLocation.LocalMachine, RootCertificate); UninstallCertificate(StoreName.Root, StoreLocation.LocalMachine, RootCertificate);
} }
} }
/// <summary> /// <summary>
/// Removes the trusted certificates from user store, optionally also from machine store /// Removes the trusted certificates from user store, optionally also from machine store
// Prompts with UAC if elevated permissions are required. Works only on Windows.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public bool RemoveTrustedRootCertificateAsAdmin(bool machineTrusted = false) public bool RemoveTrustedRootCertificateAsAdmin(bool machineTrusted = false)
...@@ -826,14 +834,14 @@ namespace Titanium.Web.Proxy.Network ...@@ -826,14 +834,14 @@ namespace Titanium.Web.Proxy.Network
{ {
return false; return false;
} }
//currentUser\Personal //currentUser\Personal
UninstallCertificate(StoreName.My, StoreLocation.CurrentUser, RootCertificate); UninstallCertificate(StoreName.My, StoreLocation.CurrentUser, RootCertificate);
var infos = new List<ProcessStartInfo>(); var infos = new List<ProcessStartInfo>();
if (!machineTrusted) if (!machineTrusted)
{ {
//currentUser\Root infos.Add(new ProcessStartInfo
infos.Add(new ProcessStartInfo()
{ {
FileName = "certutil.exe", FileName = "certutil.exe",
Arguments = "-delstore -user Root \"" + RootCertificateName + "\"", Arguments = "-delstore -user Root \"" + RootCertificateName + "\"",
...@@ -843,14 +851,15 @@ namespace Titanium.Web.Proxy.Network ...@@ -843,14 +851,15 @@ namespace Titanium.Web.Proxy.Network
ErrorDialog = false, ErrorDialog = false,
WindowStyle = ProcessWindowStyle.Hidden WindowStyle = ProcessWindowStyle.Hidden
}); });
} }
else else
{ {
infos.AddRange( infos.AddRange(
new List<ProcessStartInfo>() { new List<ProcessStartInfo>
{
//currentMachine\Personal //currentMachine\Personal
new ProcessStartInfo(){ new ProcessStartInfo
{
FileName = "certutil.exe", FileName = "certutil.exe",
Arguments = "-delstore My \"" + RootCertificateName + "\"", Arguments = "-delstore My \"" + RootCertificateName + "\"",
CreateNoWindow = true, CreateNoWindow = true,
...@@ -860,7 +869,8 @@ namespace Titanium.Web.Proxy.Network ...@@ -860,7 +869,8 @@ namespace Titanium.Web.Proxy.Network
WindowStyle = ProcessWindowStyle.Hidden WindowStyle = ProcessWindowStyle.Hidden
}, },
//currentUser\Personal & currentMachine\Personal //currentUser\Personal & currentMachine\Personal
new ProcessStartInfo(){ new ProcessStartInfo
{
FileName = "certutil.exe", FileName = "certutil.exe",
Arguments = "-delstore Root \"" + RootCertificateName + "\"", Arguments = "-delstore Root \"" + RootCertificateName + "\"",
CreateNoWindow = true, CreateNoWindow = true,
...@@ -872,7 +882,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -872,7 +882,7 @@ namespace Titanium.Web.Proxy.Network
}); });
} }
var success = true; bool success = true;
try try
{ {
foreach (var info in infos) foreach (var info in infos)
...@@ -900,12 +910,5 @@ namespace Titanium.Web.Proxy.Network ...@@ -900,12 +910,5 @@ namespace Titanium.Web.Proxy.Network
certificateCache.Clear(); certificateCache.Clear();
rootCertificate = null; rootCertificate = null;
} }
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
}
} }
} }
#if DEBUG #if DEBUG
using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using StreamExtended.Network; using StreamExtended.Network;
namespace Titanium.Web.Proxy.Network namespace Titanium.Web.Proxy.Network
{ {
class DebugCustomBufferedStream : CustomBufferedStream internal class DebugCustomBufferedStream : CustomBufferedStream
{ {
private const string basePath = @"."; private const string basePath = @".";
private static int counter; private static int counter;
public int Counter { get; } private readonly FileStream fileStreamReceived;
private readonly FileStream fileStreamSent; private readonly FileStream fileStreamSent;
private readonly FileStream fileStreamReceived;
public DebugCustomBufferedStream(Stream baseStream, int bufferSize) : base(baseStream, bufferSize) public DebugCustomBufferedStream(Stream baseStream, int bufferSize) : base(baseStream, bufferSize)
{ {
Counter = Interlocked.Increment(ref counter); Counter = Interlocked.Increment(ref counter);
...@@ -29,6 +22,8 @@ namespace Titanium.Web.Proxy.Network ...@@ -29,6 +22,8 @@ namespace Titanium.Web.Proxy.Network
fileStreamReceived = new FileStream(Path.Combine(basePath, $"{Counter}_received.dat"), FileMode.Create); fileStreamReceived = new FileStream(Path.Combine(basePath, $"{Counter}_received.dat"), FileMode.Create);
} }
public int Counter { get; }
protected override void OnDataSent(byte[] buffer, int offset, int count) protected override void OnDataSent(byte[] buffer, int offset, int count)
{ {
fileStreamSent.Write(buffer, offset, count); fileStreamSent.Write(buffer, offset, count);
......
using StreamExtended.Network; using System.Net.Sockets;
using System.Net.Sockets; using StreamExtended.Network;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Network namespace Titanium.Web.Proxy.Network
......
using StreamExtended.Network; using System;
using System;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using StreamExtended.Network;
using Titanium.Web.Proxy.Extensions; 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;
...@@ -13,6 +13,13 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -13,6 +13,13 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary> /// </summary>
internal class TcpConnection : IDisposable internal class TcpConnection : IDisposable
{ {
internal TcpConnection(ProxyServer proxyServer)
{
LastAccess = DateTime.Now;
this.proxyServer = proxyServer;
this.proxyServer.UpdateServerConnectionCount(true);
}
private ProxyServer proxyServer { get; } private ProxyServer proxyServer { get; }
internal ExternalProxy UpStreamProxy { get; set; } internal ExternalProxy UpStreamProxy { get; set; }
...@@ -23,6 +30,8 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -23,6 +30,8 @@ namespace Titanium.Web.Proxy.Network.Tcp
internal bool IsHttps { get; set; } internal bool IsHttps { get; set; }
internal bool IsHttp2Supported { get; set; }
internal bool UseUpstreamProxy { get; set; } internal bool UseUpstreamProxy { get; set; }
/// <summary> /// <summary>
...@@ -57,13 +66,6 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -57,13 +66,6 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary> /// </summary>
internal DateTime LastAccess { get; set; } internal DateTime LastAccess { get; set; }
internal TcpConnection(ProxyServer proxyServer)
{
LastAccess = DateTime.Now;
this.proxyServer = proxyServer;
this.proxyServer.UpdateServerConnectionCount(true);
}
/// <summary> /// <summary>
/// Dispose. /// Dispose.
/// </summary> /// </summary>
......
using StreamExtended.Network; using System;
using System; using System.Collections.Generic;
using System.Linq;
using System.Net; using System.Net;
using System.Net.Security; using System.Net.Security;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended.Network;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
...@@ -21,23 +22,26 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -21,23 +22,26 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <summary> /// <summary>
/// Creates a TCP connection to server /// Creates a TCP connection to server
/// </summary> /// </summary>
/// <param name="server"></param>
/// <param name="remoteHostName"></param> /// <param name="remoteHostName"></param>
/// <param name="remotePort"></param> /// <param name="remotePort"></param>
/// <param name="applicationProtocols"></param>
/// <param name="httpVersion"></param> /// <param name="httpVersion"></param>
/// <param name="isHttps"></param> /// <param name="decryptSsl"></param>
/// <param name="isConnect"></param> /// <param name="isConnect"></param>
/// <param name="proxyServer"></param>
/// <param name="upStreamEndPoint"></param> /// <param name="upStreamEndPoint"></param>
/// <param name="externalProxy"></param> /// <param name="externalProxy"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
internal async Task<TcpConnection> CreateClient(ProxyServer server, internal async Task<TcpConnection> CreateClient(string remoteHostName, int remotePort,
string remoteHostName, int remotePort, Version httpVersion, bool isHttps, List<SslApplicationProtocol> applicationProtocols, Version httpVersion, bool decryptSsl, bool isConnect,
bool isConnect, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy) ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy, CancellationToken cancellationToken)
{ {
bool useUpstreamProxy = false; bool useUpstreamProxy = false;
//check if external proxy is set for HTTP/HTTPS //check if external proxy is set for HTTP/HTTPS
if (externalProxy != null && !(externalProxy.HostName == remoteHostName && externalProxy.Port == remotePort)) if (externalProxy != null &&
!(externalProxy.HostName == remoteHostName && externalProxy.Port == remotePort))
{ {
useUpstreamProxy = true; useUpstreamProxy = true;
...@@ -51,6 +55,8 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -51,6 +55,8 @@ namespace Titanium.Web.Proxy.Network.Tcp
TcpClient client = null; TcpClient client = null;
CustomBufferedStream stream = null; CustomBufferedStream stream = null;
bool http2Supported = false;
try try
{ {
client = new TcpClient(upStreamEndPoint); client = new TcpClient(upStreamEndPoint);
...@@ -65,28 +71,30 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -65,28 +71,30 @@ namespace Titanium.Web.Proxy.Network.Tcp
await client.ConnectAsync(remoteHostName, remotePort); await client.ConnectAsync(remoteHostName, remotePort);
} }
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize); stream = new CustomBufferedStream(client.GetStream(), proxyServer.BufferSize);
if (useUpstreamProxy && (isConnect || isHttps)) if (useUpstreamProxy && (isConnect || decryptSsl))
{
var writer = new HttpRequestWriter(stream, proxyServer.BufferSize);
var connectRequest = new ConnectRequest
{ {
var writer = new HttpRequestWriter(stream, server.BufferSize); OriginalUrl = $"{remoteHostName}:{remotePort}",
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}"); HttpVersion = httpVersion,
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}"); };
await writer.WriteLineAsync($"{KnownHeaders.Connection}: {KnownHeaders.ConnectionKeepAlive}");
connectRequest.Headers.AddHeader(KnownHeaders.Connection, KnownHeaders.ConnectionKeepAlive);
if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null) if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
{ {
await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer); connectRequest.Headers.AddHeader(HttpHeader.ProxyConnectionKeepAlive);
await writer.WriteLineAsync(KnownHeaders.ProxyAuthorization + ": Basic " + connectRequest.Headers.AddHeader(HttpHeader.GetProxyAuthorizationHeader(externalProxy.UserName, externalProxy.Password));
Convert.ToBase64String(Encoding.UTF8.GetBytes(
externalProxy.UserName + ":" + externalProxy.Password)));
} }
await writer.WriteLineAsync(); await writer.WriteRequestAsync(connectRequest, cancellationToken: cancellationToken);
using (var reader = new CustomBinaryReader(stream, server.BufferSize)) using (var reader = new CustomBinaryReader(stream, proxyServer.BufferSize))
{ {
string httpStatus = await reader.ReadLineAsync(); string httpStatus = await reader.ReadLineAsync(cancellationToken);
Response.ParseResponseLine(httpStatus, out _, out int statusCode, out string statusDescription); Response.ParseResponseLine(httpStatus, out _, out int statusCode, out string statusDescription);
...@@ -96,20 +104,35 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -96,20 +104,35 @@ namespace Titanium.Web.Proxy.Network.Tcp
throw new Exception("Upstream proxy failed to create a secure tunnel"); throw new Exception("Upstream proxy failed to create a secure tunnel");
} }
await reader.ReadAndIgnoreAllLinesAsync(); await reader.ReadAndIgnoreAllLinesAsync(cancellationToken);
} }
} }
if (isHttps) if (decryptSsl)
{ {
var sslStream = new SslStream(stream, false, server.ValidateServerCertificate, server.SelectClientCertificate); var sslStream = new SslStream(stream, false, proxyServer.ValidateServerCertificate,
stream = new CustomBufferedStream(sslStream, server.BufferSize); proxyServer.SelectClientCertificate);
stream = new CustomBufferedStream(sslStream, proxyServer.BufferSize);
var options = new SslClientAuthenticationOptions();
options.ApplicationProtocols = applicationProtocols;
if (options.ApplicationProtocols == null || options.ApplicationProtocols.Count == 0)
{
options.ApplicationProtocols = SslExtensions.Http11ProtocolAsList;
}
await sslStream.AuthenticateAsClientAsync(remoteHostName, null, server.SupportedSslProtocols, server.CheckCertificateRevocation); options.TargetHost = remoteHostName;
options.ClientCertificates = null;
options.EnabledSslProtocols = proxyServer.SupportedSslProtocols;
options.CertificateRevocationCheckMode = proxyServer.CheckCertificateRevocation;
await sslStream.AuthenticateAsClientAsync(options, cancellationToken);
#if NETCOREAPP2_1
http2Supported = sslStream.NegotiatedApplicationProtocol == SslApplicationProtocol.Http2;
#endif
} }
client.ReceiveTimeout = server.ConnectionTimeOutSeconds * 1000; client.ReceiveTimeout = proxyServer.ConnectionTimeOutSeconds * 1000;
client.SendTimeout = server.ConnectionTimeOutSeconds * 1000; client.SendTimeout = proxyServer.ConnectionTimeOutSeconds * 1000;
} }
catch (Exception) catch (Exception)
{ {
...@@ -118,17 +141,18 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -118,17 +141,18 @@ namespace Titanium.Web.Proxy.Network.Tcp
throw; throw;
} }
return new TcpConnection(server) return new TcpConnection(proxyServer)
{ {
UpStreamProxy = externalProxy, UpStreamProxy = externalProxy,
UpStreamEndPoint = upStreamEndPoint, UpStreamEndPoint = upStreamEndPoint,
HostName = remoteHostName, HostName = remoteHostName,
Port = remotePort, Port = remotePort,
IsHttps = isHttps, IsHttps = decryptSsl,
IsHttp2Supported = http2Supported,
UseUpstreamProxy = useUpstreamProxy, UseUpstreamProxy = useUpstreamProxy,
TcpClient = client, TcpClient = client,
StreamReader = new CustomBinaryReader(stream, server.BufferSize), StreamReader = new CustomBinaryReader(stream, proxyServer.BufferSize),
StreamWriter = new HttpRequestWriter(stream, server.BufferSize), StreamWriter = new HttpRequestWriter(stream, proxyServer.BufferSize),
Stream = stream, Stream = stream,
Version = httpVersion Version = httpVersion
}; };
......
using System.Net;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Network.Tcp
{
/// <summary>
/// Represents a managed interface of IP Helper API TcpRow struct
/// <see href="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary>
internal class TcpRow
{
/// <summary>
/// Initializes a new instance of the <see cref="TcpRow"/> class.
/// </summary>
/// <param name="tcpRow">TcpRow struct.</param>
internal TcpRow(NativeMethods.TcpRow tcpRow)
{
ProcessId = tcpRow.owningPid;
LocalPort = tcpRow.GetLocalPort();
LocalAddress = tcpRow.localAddr;
RemotePort = tcpRow.GetRemotePort();
RemoteAddress = tcpRow.remoteAddr;
}
/// <summary>
/// Gets the local end point address.
/// </summary>
internal long LocalAddress { get; }
/// <summary>
/// Gets the local end point port.
/// </summary>
internal int LocalPort { get; }
/// <summary>
/// Gets the local end point.
/// </summary>
internal IPEndPoint LocalEndPoint => new IPEndPoint(LocalAddress, LocalPort);
/// <summary>
/// Gets the remote end point address.
/// </summary>
internal long RemoteAddress { get; }
/// <summary>
/// Gets the remote end point port.
/// </summary>
internal int RemotePort { get; }
/// <summary>
/// Gets the remote end point.
/// </summary>
internal IPEndPoint RemoteEndPoint => new IPEndPoint(RemoteAddress, RemotePort);
/// <summary>
/// Gets the process identifier.
/// </summary>
internal int ProcessId { get; }
}
}
using System.Collections;
using System.Collections.Generic;
namespace Titanium.Web.Proxy.Network.Tcp
{
/// <summary>
/// Represents collection of TcpRows
/// </summary>
/// <seealso>
/// <cref>System.Collections.Generic.IEnumerable{Proxy.Tcp.TcpRow}</cref>
/// </seealso>
internal class TcpTable : IEnumerable<TcpRow>
{
/// <summary>
/// Initializes a new instance of the <see cref="TcpTable"/> class.
/// </summary>
/// <param name="tcpRows">TcpRow collection to initialize with.</param>
internal TcpTable(IEnumerable<TcpRow> tcpRows)
{
TcpRows = tcpRows;
}
/// <summary>
/// Gets the TCP rows.
/// </summary>
internal IEnumerable<TcpRow> TcpRows { get; }
/// <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<TcpRow> GetEnumerator()
{
return TcpRows.GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
...@@ -5,6 +5,9 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -5,6 +5,9 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
{ {
internal class Common internal class Common
{ {
internal static uint NewContextAttributes = 0;
internal static SecurityInteger NewLifeTime = new SecurityInteger(0);
#region Private constants #region Private constants
private const int ISC_REQ_REPLAY_DETECT = 0x00000004; private const int ISC_REQ_REPLAY_DETECT = 0x00000004;
...@@ -14,12 +17,11 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -14,12 +17,11 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
#endregion #endregion
internal static uint NewContextAttributes = 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;
...@@ -69,7 +71,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -69,7 +71,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
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)
} }
internal enum NtlmAuthLevel internal enum NtlmAuthLevel
...@@ -86,7 +88,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -86,7 +88,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
NTLM_only, NTLM_only,
/* Use NTLMv2 only. */ /* Use NTLMv2 only. */
NTLMv2_only, NTLMv2_only
} }
#endregion #endregion
...@@ -211,7 +213,8 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -211,7 +213,8 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
//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));
var 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);
} }
} }
...@@ -267,13 +270,14 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -267,13 +270,14 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
//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);
var 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;
} }
} }
......
...@@ -37,7 +37,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -37,7 +37,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
{ {
} }
private static unsafe byte[] GetUShortBytes(byte*bytes) private static unsafe byte[] GetUShortBytes(byte* bytes)
{ {
if (BitConverter.IsLittleEndian) if (BitConverter.IsLittleEndian)
{ {
...@@ -47,7 +47,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -47,7 +47,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
return new[] { bytes[1], bytes[0] }; return new[] { bytes[1], bytes[0] };
} }
private static unsafe byte[] GetUIntBytes(byte*bytes) private static unsafe byte[] GetUIntBytes(byte* bytes)
{ {
if (BitConverter.IsLittleEndian) if (BitConverter.IsLittleEndian)
{ {
...@@ -57,7 +57,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -57,7 +57,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
return new[] { bytes[3], bytes[2], bytes[1], bytes[0] }; return new[] { bytes[3], bytes[2], bytes[1], bytes[0] };
} }
private static unsafe byte[] GetULongBytes(byte*bytes) private static unsafe byte[] GetULongBytes(byte* bytes)
{ {
if (BitConverter.IsLittleEndian) if (BitConverter.IsLittleEndian)
{ {
...@@ -117,7 +117,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -117,7 +117,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
return GetULongBytes((byte*)&value); return GetULongBytes((byte*)&value);
} }
private static unsafe 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)
{ {
...@@ -131,7 +131,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -131,7 +131,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
} }
} }
private static unsafe 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)
{ {
...@@ -149,19 +149,23 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -149,19 +149,23 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
} }
} }
private static unsafe 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)];
} }
} }
}
internal static bool ToBoolean(byte[] value, int startIndex) internal static bool ToBoolean(byte[] value, int startIndex)
{ {
......
...@@ -42,6 +42,8 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -42,6 +42,8 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
{ {
private static readonly byte[] header = { 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00 }; private static readonly byte[] header = { 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00 };
private readonly int type;
internal Message(byte[] message) internal Message(byte[] message)
{ {
type = 3; type = 3;
...@@ -58,8 +60,6 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -58,8 +60,6 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
/// </summary> /// </summary>
internal string Username { get; private set; } internal string Username { get; private set; }
private readonly int type;
internal Common.NtlmFlags Flags { get; set; } internal Common.NtlmFlags Flags { get; set; }
// methods // methods
...@@ -68,7 +68,9 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -68,7 +68,9 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
//base.Decode (message); //base.Decode (message);
if (message == null) if (message == null)
{
throw new ArgumentNullException("message"); throw new ArgumentNullException("message");
}
if (message.Length < 12) if (message.Length < 12)
{ {
...@@ -108,12 +110,13 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -108,12 +110,13 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
Username = DecodeString(message, userOff, userLen); Username = DecodeString(message, userOff, userLen);
} }
string DecodeString(byte[] buffer, int offset, int len) private 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);
} }
return Encoding.ASCII.GetString(buffer, offset, len); return Encoding.ASCII.GetString(buffer, offset, len);
} }
...@@ -122,9 +125,12 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -122,9 +125,12 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
for (int i = 0; i < header.Length; i++) for (int i = 0; i < header.Length; i++)
{ {
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;
} }
} }
} }
...@@ -16,21 +16,12 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -16,21 +16,12 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
INITIAL_TOKEN, INITIAL_TOKEN,
FINAL_TOKEN, FINAL_TOKEN,
AUTHORIZED AUTHORIZED
};
internal State()
{
Credentials = new Common.SecurityHandle(0);
Context = new Common.SecurityHandle(0);
LastSeen = DateTime.Now;
AuthState = WinAuthState.UNAUTHORIZED;
} }
/// <summary> /// <summary>
/// Credentials used to validate NTLM hashes /// Current state of the authentication process
/// </summary> /// </summary>
internal Common.SecurityHandle Credentials; internal WinAuthState AuthState;
/// <summary> /// <summary>
/// Context will be used to validate HTLM hashes /// Context will be used to validate HTLM hashes
...@@ -38,14 +29,23 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -38,14 +29,23 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
internal Common.SecurityHandle Context; internal Common.SecurityHandle Context;
/// <summary> /// <summary>
/// Timestamp needed to calculate validity of the authenticated session /// Credentials used to validate NTLM hashes
/// </summary> /// </summary>
internal DateTime LastSeen; internal Common.SecurityHandle Credentials;
/// <summary> /// <summary>
/// Current state of the authentication process /// Timestamp needed to calculate validity of the authenticated session
/// </summary> /// </summary>
internal WinAuthState AuthState; internal DateTime LastSeen;
internal State()
{
Credentials = new Common.SecurityHandle(0);
Context = new Common.SecurityHandle(0);
LastSeen = DateTime.Now;
AuthState = WinAuthState.UNAUTHORIZED;
}
internal void ResetHandles() internal void ResetHandles()
{ {
......
...@@ -53,7 +53,6 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -53,7 +53,6 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
if (result != SuccessfulResult) if (result != SuccessfulResult)
{ {
// Credentials acquire operation failed.
return null; return null;
} }
...@@ -73,7 +72,6 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -73,7 +72,6 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
if (result != IntermediateResult) if (result != IntermediateResult)
{ {
// Client challenge issue operation failed.
return null; return null;
} }
...@@ -128,7 +126,6 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -128,7 +126,6 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
if (result != SuccessfulResult) if (result != SuccessfulResult)
{ {
// Client challenge issue operation failed.
return null; return null;
} }
...@@ -172,22 +169,22 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -172,22 +169,22 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
/// <returns></returns> /// <returns></returns>
internal static bool ValidateWinAuthState(Guid requestId, State.WinAuthState expectedAuthState) internal static bool ValidateWinAuthState(Guid requestId, State.WinAuthState expectedAuthState)
{ {
var stateExists = authStates.TryGetValue(requestId, out var state); bool stateExists = authStates.TryGetValue(requestId, out var state);
if (expectedAuthState == State.WinAuthState.UNAUTHORIZED) if (expectedAuthState == State.WinAuthState.UNAUTHORIZED)
{ {
// Validation before initial token
return stateExists == false || return stateExists == false ||
state.AuthState == State.WinAuthState.UNAUTHORIZED || state.AuthState == State.WinAuthState.UNAUTHORIZED ||
state.AuthState == State.WinAuthState.AUTHORIZED; // Server may require re-authentication on an open connection state.AuthState ==
State.WinAuthState.AUTHORIZED; // Server may require re-authentication on an open connection
} }
if (expectedAuthState == State.WinAuthState.INITIAL_TOKEN) if (expectedAuthState == State.WinAuthState.INITIAL_TOKEN)
{ {
// Validation before final token
return stateExists && return stateExists &&
(state.AuthState == State.WinAuthState.INITIAL_TOKEN || (state.AuthState == State.WinAuthState.INITIAL_TOKEN ||
state.AuthState == State.WinAuthState.AUTHORIZED); // Server may require re-authentication on an open connection state.AuthState == State.WinAuthState.AUTHORIZED
); // Server may require re-authentication on an open connection
} }
throw new Exception("Unsupported validation of WinAuthState"); throw new Exception("Unsupported validation of WinAuthState");
...@@ -209,7 +206,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -209,7 +206,7 @@ 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 private static extern int InitializeSecurityContext(ref SecurityHandle phCredential, //PCredHandle
IntPtr phContext, //PCtxtHandle IntPtr phContext, //PCtxtHandle
string pszTargetName, string pszTargetName,
int fContextReq, int fContextReq,
...@@ -223,7 +220,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -223,7 +220,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
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 private static extern int InitializeSecurityContext(ref SecurityHandle phCredential, //PCredHandle
ref SecurityHandle phContext, //PCtxtHandle ref SecurityHandle phContext, //PCtxtHandle
string pszTargetName, string pszTargetName,
int fContextReq, int fContextReq,
......
...@@ -34,7 +34,9 @@ namespace Titanium.Web.Proxy.Network.WinAuth ...@@ -34,7 +34,9 @@ namespace Titanium.Web.Proxy.Network.WinAuth
/// <returns></returns> /// <returns></returns>
public static string GetFinalAuthToken(string serverHostname, string serverToken, Guid requestId) public static string GetFinalAuthToken(string serverHostname, string serverToken, Guid requestId)
{ {
var tokenBytes = WinAuthEndPoint.AcquireFinalSecurityToken(serverHostname, Convert.FromBase64String(serverToken), requestId); var tokenBytes =
WinAuthEndPoint.AcquireFinalSecurityToken(serverHostname, Convert.FromBase64String(serverToken),
requestId);
return string.Concat(" ", Convert.ToBase64String(tokenBytes)); return string.Concat(" ", Convert.ToBase64String(tokenBytes));
} }
......
using System; using System;
using System.Linq;
using System.Net; using System.Net;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions; using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
...@@ -34,7 +32,8 @@ namespace Titanium.Web.Proxy ...@@ -34,7 +32,8 @@ namespace Titanium.Web.Proxy
} }
var headerValueParts = header.Value.Split(ProxyConstants.SpaceSplit); var headerValueParts = header.Value.Split(ProxyConstants.SpaceSplit);
if (headerValueParts.Length != 2 || !headerValueParts[0].EqualsIgnoreCase(KnownHeaders.ProxyAuthorizationBasic)) if (headerValueParts.Length != 2 ||
!headerValueParts[0].EqualsIgnoreCase(KnownHeaders.ProxyAuthorizationBasic))
{ {
//Return not authorized //Return not authorized
session.WebSession.Response = CreateAuthentication407Response("Proxy Authentication Invalid"); session.WebSession.Response = CreateAuthentication407Response("Proxy Authentication Invalid");
...@@ -55,7 +54,6 @@ namespace Titanium.Web.Proxy ...@@ -55,7 +54,6 @@ namespace Titanium.Web.Proxy
bool authenticated = await AuthenticateUserFunc(username, password); bool authenticated = await AuthenticateUserFunc(username, password);
if (!authenticated) if (!authenticated)
{ {
//Return not authorized
session.WebSession.Response = CreateAuthentication407Response("Proxy Authentication Invalid"); session.WebSession.Response = CreateAuthentication407Response("Proxy Authentication Invalid");
} }
...@@ -63,7 +61,8 @@ namespace Titanium.Web.Proxy ...@@ -63,7 +61,8 @@ namespace Titanium.Web.Proxy
} }
catch (Exception e) catch (Exception e)
{ {
ExceptionFunc(new ProxyAuthorizationException("Error whilst authorizing request", session, e, httpHeaders)); ExceptionFunc(new ProxyAuthorizationException("Error whilst authorizing request", session, e,
httpHeaders));
//Return not authorized //Return not authorized
session.WebSession.Response = CreateAuthentication407Response("Proxy Authentication Invalid"); session.WebSession.Response = CreateAuthentication407Response("Proxy Authentication Invalid");
......
...@@ -4,6 +4,7 @@ using System.Linq; ...@@ -4,6 +4,7 @@ using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Authentication; using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
...@@ -25,20 +26,25 @@ namespace Titanium.Web.Proxy ...@@ -25,20 +26,25 @@ namespace Titanium.Web.Proxy
internal static readonly string UriSchemeHttp = Uri.UriSchemeHttp; internal static readonly string UriSchemeHttp = Uri.UriSchemeHttp;
internal static readonly string UriSchemeHttps = Uri.UriSchemeHttps; internal static readonly string UriSchemeHttps = Uri.UriSchemeHttps;
/// <summary>
/// An default exception log func
/// </summary>
private readonly ExceptionHandler defaultExceptionFunc = e => { };
/// <summary> /// <summary>
/// Backing field for corresponding public property /// Backing field for corresponding public property
/// </summary> /// </summary>
private int clientConnectionCount; private int clientConnectionCount;
/// <summary> /// <summary>
/// Backing field for corresponding public property /// backing exception func for exposed public property
/// </summary> /// </summary>
private int serverConnectionCount; private ExceptionHandler exceptionFunc;
/// <summary> /// <summary>
/// A object that creates tcp connection to server /// Backing field for corresponding public property
/// </summary> /// </summary>
private TcpConnectionFactory tcpConnectionFactory { get; } private int serverConnectionCount;
/// <summary> /// <summary>
/// Manaage upstream proxy detection /// Manaage upstream proxy detection
...@@ -46,19 +52,58 @@ namespace Titanium.Web.Proxy ...@@ -46,19 +52,58 @@ namespace Titanium.Web.Proxy
private WinHttpWebProxyFinder systemProxyResolver; private WinHttpWebProxyFinder systemProxyResolver;
/// <summary> /// <summary>
/// Manage system proxy settings /// Constructor
/// </summary> /// </summary>
private SystemProxyManager systemProxySettingsManager { get; } /// <param name="userTrustRootCertificate"></param>
/// <param name="machineTrustRootCertificate">
/// Note:setting machineTrustRootCertificate to true will force
/// userTrustRootCertificate to true
/// </param>
/// <param name="trustRootCertificateAsAdmin"></param>
public ProxyServer(bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false,
bool trustRootCertificateAsAdmin = false) : this(null, null, userTrustRootCertificate,
machineTrustRootCertificate, trustRootCertificateAsAdmin)
{
}
/// <summary> /// <summary>
/// An default exception log func /// Constructor.
/// </summary>
/// <param name="rootCertificateName">Name of root certificate.</param>
/// <param name="rootCertificateIssuerName">Name of root certificate issuer.</param>
/// <param name="userTrustRootCertificate"></param>
/// <param name="machineTrustRootCertificate">
/// Note:setting machineTrustRootCertificate to true will force
/// userTrustRootCertificate to true
/// </param>
/// <param name="trustRootCertificateAsAdmin"></param>
public ProxyServer(string rootCertificateName, string rootCertificateIssuerName,
bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false,
bool trustRootCertificateAsAdmin = false)
{
//default values
ConnectionTimeOutSeconds = 30;
ProxyEndPoints = new List<ProxyEndPoint>();
tcpConnectionFactory = new TcpConnectionFactory();
if (!RunTime.IsRunningOnMono && RunTime.IsWindows)
{
systemProxySettingsManager = new SystemProxyManager();
}
CertificateManager = new CertificateManager(rootCertificateName, rootCertificateIssuerName,
userTrustRootCertificate, machineTrustRootCertificate, trustRootCertificateAsAdmin, ExceptionFunc);
}
/// <summary>
/// A object that creates tcp connection to server
/// </summary> /// </summary>
private readonly Lazy<ExceptionHandler> defaultExceptionFunc = new Lazy<ExceptionHandler>(() => (e => { })); private TcpConnectionFactory tcpConnectionFactory { get; }
/// <summary> /// <summary>
/// backing exception func for exposed public property /// Manage system proxy settings
/// </summary> /// </summary>
private ExceptionHandler exceptionFunc; private SystemProxyManager systemProxySettingsManager { get; }
/// <summary> /// <summary>
/// Is the proxy currently running /// Is the proxy currently running
...@@ -81,9 +126,9 @@ namespace Titanium.Web.Proxy ...@@ -81,9 +126,9 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// Should we check for certificare revocation during SSL authentication to servers /// Should we check for certificare revocation during SSL authentication to servers
/// Note: If enabled can reduce performance (Default disabled) /// Note: If enabled can reduce performance (Default: NoCheck)
/// </summary> /// </summary>
public bool CheckCertificateRevocation { get; set; } public X509RevocationMode CheckCertificateRevocation { get; set; }
/// <summary> /// <summary>
/// Does this proxy uses the HTTP protocol 100 continue behaviour strictly? /// Does this proxy uses the HTTP protocol 100 continue behaviour strictly?
...@@ -153,6 +198,41 @@ namespace Titanium.Web.Proxy ...@@ -153,6 +198,41 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public List<ProxyEndPoint> ProxyEndPoints { get; set; } public List<ProxyEndPoint> ProxyEndPoints { get; set; }
/// <summary>
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP(S) requests
/// return the ExternalProxy object with valid credentials
/// </summary>
public Func<SessionEventArgsBase, Task<ExternalProxy>> GetCustomUpStreamProxyFunc { get; set; }
/// <summary>
/// Callback for error events in proxy
/// </summary>
public ExceptionHandler ExceptionFunc
{
get => exceptionFunc ?? defaultExceptionFunc;
set => exceptionFunc = value;
}
/// <summary>
/// A callback to authenticate clients
/// Parameters are username, password provided by client
/// return true for successful authentication
/// </summary>
public Func<string, string, Task<bool>> AuthenticateUserFunc { get; set; }
/// <summary>
/// Dispose Proxy.
/// </summary>
public void Dispose()
{
if (ProxyRunning)
{
Stop();
}
CertificateManager?.Dispose();
}
/// <summary> /// <summary>
/// Occurs when client connection count changed. /// Occurs when client connection count changed.
/// </summary> /// </summary>
...@@ -173,12 +253,6 @@ namespace Titanium.Web.Proxy ...@@ -173,12 +253,6 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public event AsyncEventHandler<CertificateSelectionEventArgs> ClientCertificateSelectionCallback; public event AsyncEventHandler<CertificateSelectionEventArgs> ClientCertificateSelectionCallback;
/// <summary>
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP(S) requests
/// return the ExternalProxy object with valid credentials
/// </summary>
public Func<SessionEventArgsBase, Task<ExternalProxy>> GetCustomUpStreamProxyFunc { get; set; }
/// <summary> /// <summary>
/// Intercept request to server /// Intercept request to server
/// </summary> /// </summary>
...@@ -194,62 +268,14 @@ namespace Titanium.Web.Proxy ...@@ -194,62 +268,14 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public event AsyncEventHandler<SessionEventArgs> AfterResponse; public event AsyncEventHandler<SessionEventArgs> AfterResponse;
/// <summary>
/// Callback for error events in proxy
/// </summary>
public ExceptionHandler ExceptionFunc
{
get => exceptionFunc ?? defaultExceptionFunc.Value;
set => exceptionFunc = value;
}
/// <summary>
/// A callback to authenticate clients
/// Parameters are username, password provided by client
/// return true for successful authentication
/// </summary>
public Func<string, string, Task<bool>> AuthenticateUserFunc { get; set; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="userTrustRootCertificate"></param>
/// <param name="machineTrustRootCertificate">Note:setting machineTrustRootCertificate to true will force userTrustRootCertificate to true</param>
/// <param name="trustRootCertificateAsAdmin"></param>
public ProxyServer(bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false, bool trustRootCertificateAsAdmin = false) : this(null, null, userTrustRootCertificate, machineTrustRootCertificate, trustRootCertificateAsAdmin)
{
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="rootCertificateName">Name of root certificate.</param>
/// <param name="rootCertificateIssuerName">Name of root certificate issuer.</param>
/// <param name="userTrustRootCertificate"></param>
/// <param name="machineTrustRootCertificate">Note:setting machineTrustRootCertificate to true will force userTrustRootCertificate to true</param>
/// <param name="trustRootCertificateAsAdmin"></param>
public ProxyServer(string rootCertificateName, string rootCertificateIssuerName, bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false, bool trustRootCertificateAsAdmin = false)
{
//default values
ConnectionTimeOutSeconds = 30;
ProxyEndPoints = new List<ProxyEndPoint>();
tcpConnectionFactory = new TcpConnectionFactory();
if (!RunTime.IsRunningOnMono && RunTime.IsWindows)
{
systemProxySettingsManager = new SystemProxyManager();
}
CertificateManager = new CertificateManager(rootCertificateName, rootCertificateIssuerName, userTrustRootCertificate, machineTrustRootCertificate, trustRootCertificateAsAdmin, ExceptionFunc);
}
/// <summary> /// <summary>
/// Add a proxy end point /// Add a proxy end point
/// </summary> /// </summary>
/// <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) && endPoint.Port != 0 && x.Port == endPoint.Port)) if (ProxyEndPoints.Any(x =>
x.IpAddress.Equals(endPoint.IpAddress) && endPoint.Port != 0 && x.Port == endPoint.Port))
{ {
throw new Exception("Cannot add another endpoint to same port & ip address"); throw new Exception("Cannot add another endpoint to same port & ip address");
} }
...@@ -319,7 +345,6 @@ namespace Titanium.Web.Proxy ...@@ -319,7 +345,6 @@ namespace Titanium.Web.Proxy
if (isHttps) if (isHttps)
{ {
CertificateManager.EnsureRootCertificate(); CertificateManager.EnsureRootCertificate();
//If certificate was trusted by the machine //If certificate was trusted by the machine
...@@ -375,7 +400,8 @@ namespace Titanium.Web.Proxy ...@@ -375,7 +400,8 @@ namespace Titanium.Web.Proxy
if (protocolType != ProxyProtocolType.None) if (protocolType != ProxyProtocolType.None)
{ {
Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System {2} Proxy", endPoint.IpAddress, endPoint.Port, proxyType); Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System {2} Proxy", endPoint.IpAddress,
endPoint.Port, proxyType);
} }
} }
...@@ -456,7 +482,6 @@ namespace Titanium.Web.Proxy ...@@ -456,7 +482,6 @@ namespace Titanium.Web.Proxy
if (protocolToRemove != ProxyProtocolType.None) if (protocolToRemove != ProxyProtocolType.None)
{ {
//do not restore to any of listening address when we quit
systemProxySettingsManager.RemoveProxy(protocolToRemove, false); systemProxySettingsManager.RemoveProxy(protocolToRemove, false);
} }
} }
...@@ -482,7 +507,6 @@ namespace Titanium.Web.Proxy ...@@ -482,7 +507,6 @@ namespace Titanium.Web.Proxy
if (RunTime.IsWindows && !RunTime.IsRunningOnMono) if (RunTime.IsWindows && !RunTime.IsRunningOnMono)
{ {
//clear orphaned windows auth states every 2 minutes
WinAuthEndPoint.ClearIdleStates(2); WinAuthEndPoint.ClearIdleStates(2);
} }
} }
...@@ -499,7 +523,8 @@ namespace Titanium.Web.Proxy ...@@ -499,7 +523,8 @@ namespace Titanium.Web.Proxy
if (!RunTime.IsRunningOnMono && RunTime.IsWindows) if (!RunTime.IsRunningOnMono && RunTime.IsWindows)
{ {
bool 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)
{ {
...@@ -519,19 +544,6 @@ namespace Titanium.Web.Proxy ...@@ -519,19 +544,6 @@ namespace Titanium.Web.Proxy
ProxyRunning = false; ProxyRunning = false;
} }
/// <summary>
/// Dispose Proxy.
/// </summary>
public void Dispose()
{
if (ProxyRunning)
{
Stop();
}
CertificateManager?.Dispose();
}
/// <summary> /// <summary>
/// Listen on the given end point on local machine /// Listen on the given end point on local machine
/// </summary> /// </summary>
...@@ -550,7 +562,8 @@ namespace Titanium.Web.Proxy ...@@ -550,7 +562,8 @@ namespace Titanium.Web.Proxy
} }
catch (SocketException ex) catch (SocketException ex)
{ {
var pex = new Exception($"Endpoint {endPoint} failed to start. Check inner exception and exception data for details.", ex); var pex = new Exception(
$"Endpoint {endPoint} failed to start. Check inner exception and exception data for details.", ex);
pex.Data.Add("ipAddress", endPoint.IpAddress); pex.Data.Add("ipAddress", endPoint.IpAddress);
pex.Data.Add("port", endPoint.Port); pex.Data.Add("port", endPoint.Port);
throw pex; throw pex;
...@@ -564,7 +577,10 @@ namespace Titanium.Web.Proxy ...@@ -564,7 +577,10 @@ namespace Titanium.Web.Proxy
private void ValidateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint) private void ValidateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint)
{ {
if (endPoint == null) if (endPoint == null)
{
throw new ArgumentNullException(nameof(endPoint)); throw new ArgumentNullException(nameof(endPoint));
}
if (ProxyEndPoints.Contains(endPoint) == false) if (ProxyEndPoints.Contains(endPoint) == false)
{ {
throw new Exception("Cannot set endPoints not added to proxy as system proxy"); throw new Exception("Cannot set endPoints not added to proxy as system proxy");
...@@ -579,8 +595,11 @@ namespace Titanium.Web.Proxy ...@@ -579,8 +595,11 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// Gets the system up stream proxy. /// Gets the system up stream proxy.
/// </summary> /// </summary>
/// <param name="sessionEventArgs">The <see cref="SessionEventArgs"/> instance containing the event data.</param> /// <param name="sessionEventArgs">The <see cref="SessionEventArgs" /> instance containing the event data.</param>
/// <returns><see cref="ExternalProxy"/> instance containing valid proxy configuration from PAC/WAPD scripts if any exists.</returns> /// <returns>
/// <see cref="ExternalProxy" /> instance containing valid proxy configuration from PAC/WAPD scripts if any
/// exists.
/// </returns>
private Task<ExternalProxy> GetSystemUpStreamProxy(SessionEventArgsBase sessionEventArgs) private Task<ExternalProxy> GetSystemUpStreamProxy(SessionEventArgsBase sessionEventArgs)
{ {
var proxy = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri); var proxy = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri);
...@@ -616,10 +635,7 @@ namespace Titanium.Web.Proxy ...@@ -616,10 +635,7 @@ namespace Titanium.Web.Proxy
if (tcpClient != null) if (tcpClient != null)
{ {
Task.Run(async () => Task.Run(async () => { await HandleClient(tcpClient, endPoint); });
{
await HandleClient(tcpClient, endPoint);
});
} }
// Get the listener that handles the client request. // Get the listener that handles the client request.
......
using System; using System;
using System.IO;
using System.Linq;
using System.Net; using System.Net;
using System.Net.Security;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Authentication;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended;
using StreamExtended.Helpers;
using StreamExtended.Network; using StreamExtended.Network;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions; using Titanium.Web.Proxy.Exceptions;
...@@ -25,315 +20,11 @@ namespace Titanium.Web.Proxy ...@@ -25,315 +20,11 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
partial class ProxyServer partial class ProxyServer
{ {
private static readonly Regex uriSchemeRegex = new Regex("^[a-z]*://", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex uriSchemeRegex =
new Regex("^[a-z]*://", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private bool isWindowsAuthenticationEnabledAndSupported => EnableWinAuth && RunTime.IsWindows && !RunTime.IsRunningOnMono; private bool isWindowsAuthenticationEnabledAndSupported =>
EnableWinAuth && RunTime.IsWindows && !RunTime.IsRunningOnMono;
/// <summary>
/// This is called when client is aware of proxy
/// So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
/// </summary>
/// <param name="endPoint"></param>
/// <param name="tcpClient"></param>
/// <returns></returns>
private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient)
{
var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
try
{
string connectHostname = null;
ConnectRequest connectRequest = null;
//Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request)
if (await HttpHelper.IsConnectMethod(clientStream) == 1)
{
//read the first line HTTP command
string httpCmd = await clientStreamReader.ReadLineAsync();
if (string.IsNullOrEmpty(httpCmd))
{
return;
}
Request.ParseRequestLine(httpCmd, out string _, out string httpUrl, out var version);
var httpRemoteUri = new Uri("http://" + httpUrl);
connectHostname = httpRemoteUri.Host;
connectRequest = new ConnectRequest
{
RequestUri = httpRemoteUri,
OriginalUrl = httpUrl,
HttpVersion = version,
};
await HeaderParser.ReadHeaders(clientStreamReader, connectRequest.Headers);
var connectArgs = new TunnelConnectSessionEventArgs(BufferSize, endPoint, connectRequest, ExceptionFunc);
connectArgs.ProxyClient.TcpClient = tcpClient;
connectArgs.ProxyClient.ClientStream = clientStream;
await endPoint.InvokeBeforeTunnelConnectRequest(this, connectArgs, ExceptionFunc);
//filter out excluded host names
bool decryptSsl = endPoint.DecryptSsl && connectArgs.DecryptSsl;
if (connectArgs.BlockConnect)
{
if (connectArgs.WebSession.Response.StatusCode == 0)
{
connectArgs.WebSession.Response = new Response
{
HttpVersion = HttpHeader.Version11,
StatusCode = (int)HttpStatusCode.Forbidden,
StatusDescription = "Forbidden",
};
}
//send the response
await clientStreamWriter.WriteResponseAsync(connectArgs.WebSession.Response);
return;
}
if (await CheckAuthorization(connectArgs) == false)
{
await endPoint.InvokeBeforeTunnectConnectResponse(this, connectArgs, ExceptionFunc);
//send the response
await clientStreamWriter.WriteResponseAsync(connectArgs.WebSession.Response);
return;
}
//write back successfull CONNECT response
var response = ConnectResponse.CreateSuccessfullConnectResponse(version);
// Set ContentLength explicitly to properly handle HTTP 1.0
response.ContentLength = 0;
response.Headers.FixProxyHeaders();
connectArgs.WebSession.Response = response;
await clientStreamWriter.WriteResponseAsync(response);
var clientHelloInfo = await SslTools.PeekClientHello(clientStream);
bool isClientHello = clientHelloInfo != null;
if (isClientHello)
{
connectRequest.ClientHelloInfo = clientHelloInfo;
}
await endPoint.InvokeBeforeTunnectConnectResponse(this, connectArgs, ExceptionFunc, isClientHello);
if (decryptSsl && isClientHello)
{
connectRequest.RequestUri = new Uri("https://" + httpUrl);
SslStream sslStream = null;
try
{
sslStream = new SslStream(clientStream);
string certName = HttpHelper.GetWildCardDomainName(connectHostname);
var certificate = endPoint.GenericCertificate ?? await CertificateManager.CreateCertificateAsync(certName);
//Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false, SupportedSslProtocols, false);
//HTTPS server created - we can now decrypt the client's traffic
clientStream = new CustomBufferedStream(sslStream, BufferSize);
clientStreamReader.Dispose();
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
}
catch (Exception e)
{
ExceptionFunc(new Exception($"Could'nt authenticate client '{connectHostname}' with fake certificate.", e));
sslStream?.Dispose();
return;
}
if (await HttpHelper.IsConnectMethod(clientStream) == -1)
{
// It can be for example some Google (Cloude Messaging for Chrome) magic
decryptSsl = false;
}
}
//Hostname is excluded or it is not an HTTPS connect
if (!decryptSsl || !isClientHello)
{
//create new connection
using (var connection = await GetServerConnection(connectArgs, true))
{
if (isClientHello)
{
int available = clientStream.Available;
if (available > 0)
{
//send the buffered data
var data = BufferPool.GetBuffer(BufferSize);
try
{
// clientStream.Available sbould be at most BufferSize because it is using the same buffer size
await clientStream.ReadAsync(data, 0, available);
await connection.StreamWriter.WriteAsync(data, 0, available, true);
}
finally
{
BufferPool.ReturnBuffer(data);
}
}
var serverHelloInfo = await SslTools.PeekServerHello(connection.Stream);
((ConnectResponse)connectArgs.WebSession.Response).ServerHelloInfo = serverHelloInfo;
}
await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize,
(buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); },
(buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); },
ExceptionFunc);
}
return;
}
}
//Now create the request
await HandleHttpSessionRequest(tcpClient, clientStream, clientStreamReader, clientStreamWriter, connectHostname, endPoint, connectRequest);
}
catch (ProxyHttpException e)
{
ExceptionFunc(e);
}
catch (IOException e)
{
ExceptionFunc(new Exception("Connection was aborted", e));
}
catch (SocketException e)
{
ExceptionFunc(new Exception("Could not connect", e));
}
catch (Exception e)
{
ExceptionFunc(new Exception("Error occured in whilst handling the client", e));
}
finally
{
clientStreamReader.Dispose();
clientStream.Dispose();
}
}
/// <summary>
/// This is called when this proxy acts as a reverse proxy (like a real http server)
/// So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
/// </summary>
/// <param name="endPoint"></param>
/// <param name="tcpClient"></param>
/// <returns></returns>
private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient)
{
var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
try
{
var clientHelloInfo = await SslTools.PeekClientHello(clientStream);
var isHttps = clientHelloInfo != null;
string httpsHostName = null;
if (isHttps)
{
httpsHostName = clientHelloInfo.GetServerName() ?? endPoint.GenericCertificateName;
if (endPoint.DecryptSsl)
{
SslStream sslStream = null;
try
{
sslStream = new SslStream(clientStream);
string certName = HttpHelper.GetWildCardDomainName(httpsHostName);
var certificate = await CertificateManager.CreateCertificateAsync(certName);
//Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls, false);
//HTTPS server created - we can now decrypt the client's traffic
clientStream = new CustomBufferedStream(sslStream, BufferSize);
clientStreamReader.Dispose();
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
}
catch (Exception e)
{
ExceptionFunc(new Exception($"Could'nt authenticate client '{httpsHostName}' with fake certificate.", e));
sslStream?.Dispose();
return;
}
}
else
{
//create new connection
var connection = new TcpClient(UpStreamEndPoint);
await connection.ConnectAsync(httpsHostName, endPoint.Port);
connection.ReceiveTimeout = ConnectionTimeOutSeconds * 1000;
connection.SendTimeout = ConnectionTimeOutSeconds * 1000;
using (connection)
{
var serverStream = connection.GetStream();
int available = clientStream.Available;
if (available > 0)
{
//send the buffered data
var data = BufferPool.GetBuffer(BufferSize);
try
{
// clientStream.Available sbould be at most BufferSize because it is using the same buffer size
await clientStream.ReadAsync(data, 0, available);
await serverStream.WriteAsync(data, 0, available);
await serverStream.FlushAsync();
}
finally
{
BufferPool.ReturnBuffer(data);
}
}
//var serverHelloInfo = await SslTools.PeekServerHello(serverStream);
await TcpHelper.SendRaw(clientStream, serverStream, BufferSize,
null, null, ExceptionFunc);
}
}
}
//HTTPS server created - we can now decrypt the client's traffic
//Now create the request
await HandleHttpSessionRequest(tcpClient, clientStream, clientStreamReader, clientStreamWriter,
isHttps ? httpsHostName : null, endPoint, null, true);
}
finally
{
clientStreamReader.Dispose();
clientStream.Dispose();
}
}
/// <summary> /// <summary>
/// This is the core request handler method for a particular connection from client /// This is the core request handler method for a particular connection from client
...@@ -344,15 +35,19 @@ namespace Titanium.Web.Proxy ...@@ -344,15 +35,19 @@ namespace Titanium.Web.Proxy
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param> /// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param> /// <param name="clientStreamWriter"></param>
/// <param name="cancellationTokenSource"></param>
/// <param name="httpsConnectHostname"></param> /// <param name="httpsConnectHostname"></param>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
/// <param name="connectRequest"></param> /// <param name="connectRequest"></param>
/// <param name="isTransparentEndPoint"></param> /// <param name="isTransparentEndPoint"></param>
/// <returns></returns> /// <returns></returns>
private async Task HandleHttpSessionRequest(TcpClient client, CustomBufferedStream clientStream, private async Task HandleHttpSessionRequest(ProxyEndPoint endPoint, TcpClient client,
CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, string httpsConnectHostname, CustomBufferedStream clientStream, CustomBinaryReader clientStreamReader,
ProxyEndPoint endPoint, ConnectRequest connectRequest, bool isTransparentEndPoint = false) HttpResponseWriter clientStreamWriter,
CancellationTokenSource cancellationTokenSource, string httpsConnectHostname, ConnectRequest connectRequest,
bool isTransparentEndPoint = false)
{ {
var cancellationToken = cancellationTokenSource.Token;
TcpConnection connection = null; TcpConnection connection = null;
try try
...@@ -362,13 +57,14 @@ namespace Titanium.Web.Proxy ...@@ -362,13 +57,14 @@ namespace Titanium.Web.Proxy
while (true) while (true)
{ {
// read the request line // read the request line
string httpCmd = await clientStreamReader.ReadLineAsync(); string httpCmd = await clientStreamReader.ReadLineAsync(cancellationToken);
if (string.IsNullOrEmpty(httpCmd)) if (string.IsNullOrEmpty(httpCmd))
{ {
return; return;
} }
var args = new SessionEventArgs(BufferSize, endPoint, ExceptionFunc) var args = new SessionEventArgs(BufferSize, endPoint, cancellationTokenSource, ExceptionFunc)
{ {
ProxyClient = { TcpClient = client }, ProxyClient = { TcpClient = client },
WebSession = { ConnectRequest = connectRequest } WebSession = { ConnectRequest = connectRequest }
...@@ -378,10 +74,12 @@ namespace Titanium.Web.Proxy ...@@ -378,10 +74,12 @@ namespace Titanium.Web.Proxy
{ {
try try
{ {
Request.ParseRequestLine(httpCmd, out string httpMethod, out string httpUrl, out var version); Request.ParseRequestLine(httpCmd, out string httpMethod, out string httpUrl,
out var version);
//Read the request headers in to unique and non-unique header collections //Read the request headers in to unique and non-unique header collections
await HeaderParser.ReadHeaders(clientStreamReader, args.WebSession.Request.Headers); await HeaderParser.ReadHeaders(clientStreamReader, args.WebSession.Request.Headers,
cancellationToken);
Uri httpRemoteUri; Uri httpRemoteUri;
if (uriSchemeRegex.IsMatch(httpUrl)) if (uriSchemeRegex.IsMatch(httpUrl))
...@@ -404,7 +102,8 @@ namespace Titanium.Web.Proxy ...@@ -404,7 +102,8 @@ namespace Titanium.Web.Proxy
hostAndPath += httpUrl; hostAndPath += httpUrl;
} }
string url = string.Concat(httpsConnectHostname == null ? "http://" : "https://", hostAndPath); string url = string.Concat(httpsConnectHostname == null ? "http://" : "https://",
hostAndPath);
try try
{ {
httpRemoteUri = new Uri(url); httpRemoteUri = new Uri(url);
...@@ -426,12 +125,14 @@ namespace Titanium.Web.Proxy ...@@ -426,12 +125,14 @@ namespace Titanium.Web.Proxy
args.ProxyClient.ClientStreamWriter = clientStreamWriter; args.ProxyClient.ClientStreamWriter = clientStreamWriter;
//proxy authorization check //proxy authorization check
if (!args.IsTransparent && httpsConnectHostname == null && await CheckAuthorization(args) == false) if (!args.IsTransparent && httpsConnectHostname == null &&
await CheckAuthorization(args) == false)
{ {
await InvokeBeforeResponse(args); await InvokeBeforeResponse(args);
//send the response //send the response
await clientStreamWriter.WriteResponseAsync(args.WebSession.Response); await clientStreamWriter.WriteResponseAsync(args.WebSession.Response,
cancellationToken: cancellationToken);
return; return;
} }
...@@ -446,7 +147,7 @@ namespace Titanium.Web.Proxy ...@@ -446,7 +147,7 @@ namespace Titanium.Web.Proxy
//so that we can send it after authentication in WinAuthHandler.cs //so that we can send it after authentication in WinAuthHandler.cs
if (isWindowsAuthenticationEnabledAndSupported && request.HasBody) if (isWindowsAuthenticationEnabledAndSupported && request.HasBody)
{ {
await args.GetRequestBody(); await args.GetRequestBody(cancellationToken);
} }
request.OriginalHasBody = request.HasBody; request.OriginalHasBody = request.HasBody;
...@@ -459,7 +160,7 @@ namespace Titanium.Web.Proxy ...@@ -459,7 +160,7 @@ namespace Titanium.Web.Proxy
if (request.CancelRequest) if (request.CancelRequest)
{ {
//syphon out the request body from client before setting the new body //syphon out the request body from client before setting the new body
await args.SyphonOutBodyAsync(true); await args.SyphonOutBodyAsync(true, cancellationToken);
await HandleHttpSessionResponse(args); await HandleHttpSessionResponse(args);
...@@ -473,9 +174,10 @@ namespace Titanium.Web.Proxy ...@@ -473,9 +174,10 @@ namespace Titanium.Web.Proxy
//create a new connection if hostname/upstream end point changes //create a new connection if hostname/upstream end point changes
if (connection != null if (connection != null
&& (!connection.HostName.Equals(request.RequestUri.Host, StringComparison.OrdinalIgnoreCase) && (!connection.HostName.Equals(request.RequestUri.Host,
|| (args.WebSession.UpStreamEndPoint != null StringComparison.OrdinalIgnoreCase)
&& !args.WebSession.UpStreamEndPoint.Equals(connection.UpStreamEndPoint)))) || args.WebSession.UpStreamEndPoint != null
&& !args.WebSession.UpStreamEndPoint.Equals(connection.UpStreamEndPoint)))
{ {
connection.Dispose(); connection.Dispose();
connection = null; connection = null;
...@@ -483,28 +185,32 @@ namespace Titanium.Web.Proxy ...@@ -483,28 +185,32 @@ namespace Titanium.Web.Proxy
if (connection == null) if (connection == null)
{ {
connection = await GetServerConnection(args, false); connection = await GetServerConnection(args, false, cancellationToken);
} }
//if upgrading to websocket then relay the requet without reading the contents //if upgrading to websocket then relay the requet without reading the contents
if (request.UpgradeToWebSocket) if (request.UpgradeToWebSocket)
{ {
//prepare the prefix content //prepare the prefix content
await connection.StreamWriter.WriteLineAsync(httpCmd); await connection.StreamWriter.WriteLineAsync(httpCmd, cancellationToken);
await connection.StreamWriter.WriteHeadersAsync(request.Headers); await connection.StreamWriter.WriteHeadersAsync(request.Headers,
string httpStatus = await connection.StreamReader.ReadLineAsync(); cancellationToken: cancellationToken);
string httpStatus = await connection.StreamReader.ReadLineAsync(cancellationToken);
Response.ParseResponseLine(httpStatus, out var responseVersion, out int responseStatusCode, Response.ParseResponseLine(httpStatus, out var responseVersion,
out int responseStatusCode,
out string responseStatusDescription); out string responseStatusDescription);
response.HttpVersion = responseVersion; response.HttpVersion = responseVersion;
response.StatusCode = responseStatusCode; response.StatusCode = responseStatusCode;
response.StatusDescription = responseStatusDescription; response.StatusDescription = responseStatusDescription;
await HeaderParser.ReadHeaders(connection.StreamReader, response.Headers); await HeaderParser.ReadHeaders(connection.StreamReader, response.Headers,
cancellationToken);
if (!args.IsTransparent) if (!args.IsTransparent)
{ {
await clientStreamWriter.WriteResponseAsync(response); await clientStreamWriter.WriteResponseAsync(response,
cancellationToken: cancellationToken);
} }
//If user requested call back then do it //If user requested call back then do it
...@@ -516,7 +222,7 @@ namespace Titanium.Web.Proxy ...@@ -516,7 +222,7 @@ namespace Titanium.Web.Proxy
await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize, await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize,
(buffer, offset, count) => { args.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { args.OnDataSent(buffer, offset, count); },
(buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); }, (buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); },
ExceptionFunc); cancellationTokenSource, ExceptionFunc);
return; return;
} }
...@@ -526,7 +232,6 @@ namespace Titanium.Web.Proxy ...@@ -526,7 +232,6 @@ namespace Titanium.Web.Proxy
if (args.WebSession.ServerConnection == null) if (args.WebSession.ServerConnection == null)
{ {
//server connection was closed
return; return;
} }
...@@ -535,6 +240,11 @@ namespace Titanium.Web.Proxy ...@@ -535,6 +240,11 @@ namespace Titanium.Web.Proxy
{ {
return; return;
} }
if (cancellationTokenSource.IsCancellationRequested)
{
throw new Exception("Session was terminated by user.");
}
} }
catch (Exception e) when (!(e is ProxyHttpException)) catch (Exception e) when (!(e is ProxyHttpException))
{ {
...@@ -569,6 +279,7 @@ namespace Titanium.Web.Proxy ...@@ -569,6 +279,7 @@ namespace Titanium.Web.Proxy
{ {
try try
{ {
var cancellationToken = args.CancellationTokenSource.Token;
var request = args.WebSession.Request; var request = args.WebSession.Request;
request.Locked = true; request.Locked = true;
...@@ -579,7 +290,7 @@ namespace Titanium.Web.Proxy ...@@ -579,7 +290,7 @@ namespace Titanium.Web.Proxy
if (request.ExpectContinue) if (request.ExpectContinue)
{ {
args.WebSession.SetConnection(connection); args.WebSession.SetConnection(connection);
await args.WebSession.SendRequest(Enable100ContinueBehaviour, args.IsTransparent); await args.WebSession.SendRequest(Enable100ContinueBehaviour, args.IsTransparent, cancellationToken);
} }
//If 100 continue was the response inform that to the client //If 100 continue was the response inform that to the client
...@@ -589,13 +300,15 @@ namespace Titanium.Web.Proxy ...@@ -589,13 +300,15 @@ namespace Titanium.Web.Proxy
if (request.Is100Continue) if (request.Is100Continue)
{ {
await clientStreamWriter.WriteResponseStatusAsync(args.WebSession.Response.HttpVersion, (int)HttpStatusCode.Continue, "Continue"); await clientStreamWriter.WriteResponseStatusAsync(args.WebSession.Response.HttpVersion,
await clientStreamWriter.WriteLineAsync(); (int)HttpStatusCode.Continue, "Continue", cancellationToken);
await clientStreamWriter.WriteLineAsync(cancellationToken);
} }
else if (request.ExpectationFailed) else if (request.ExpectationFailed)
{ {
await clientStreamWriter.WriteResponseStatusAsync(args.WebSession.Response.HttpVersion, (int)HttpStatusCode.ExpectationFailed, "Expectation Failed"); await clientStreamWriter.WriteResponseStatusAsync(args.WebSession.Response.HttpVersion,
await clientStreamWriter.WriteLineAsync(); (int)HttpStatusCode.ExpectationFailed, "Expectation Failed", cancellationToken);
await clientStreamWriter.WriteLineAsync(cancellationToken);
} }
} }
...@@ -603,27 +316,25 @@ namespace Titanium.Web.Proxy ...@@ -603,27 +316,25 @@ namespace Titanium.Web.Proxy
if (!request.ExpectContinue) if (!request.ExpectContinue)
{ {
args.WebSession.SetConnection(connection); args.WebSession.SetConnection(connection);
await args.WebSession.SendRequest(Enable100ContinueBehaviour, args.IsTransparent); await args.WebSession.SendRequest(Enable100ContinueBehaviour, args.IsTransparent, cancellationToken);
} }
//check if content-length is > 0 //check if content-length is > 0
if (request.ContentLength > 0) if (request.ContentLength > 0)
{ {
//If request was modified by user
if (request.IsBodyRead) if (request.IsBodyRead)
{ {
var writer = args.WebSession.ServerConnection.StreamWriter; var writer = args.WebSession.ServerConnection.StreamWriter;
await writer.WriteBodyAsync(body, request.IsChunked); await writer.WriteBodyAsync(body, request.IsChunked, cancellationToken);
} }
else else
{ {
if (!request.ExpectationFailed) if (!request.ExpectationFailed)
{ {
//If its a post/put/patch request, then read the client html body and send it to server
if (request.HasBody) if (request.HasBody)
{ {
HttpWriter writer = args.WebSession.ServerConnection.StreamWriter; HttpWriter writer = args.WebSession.ServerConnection.StreamWriter;
await args.CopyRequestBodyAsync(writer, TransformationMode.None); await args.CopyRequestBodyAsync(writer, TransformationMode.None, cancellationToken);
} }
} }
} }
...@@ -646,8 +357,9 @@ namespace Titanium.Web.Proxy ...@@ -646,8 +357,9 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
/// <param name="args"></param> /// <param name="args"></param>
/// <param name="isConnect"></param> /// <param name="isConnect"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
private async Task<TcpConnection> GetServerConnection(SessionEventArgsBase args, bool isConnect) private async Task<TcpConnection> GetServerConnection(SessionEventArgsBase args, bool isConnect, CancellationToken cancellationToken)
{ {
ExternalProxy customUpStreamProxy = null; ExternalProxy customUpStreamProxy = null;
...@@ -659,13 +371,14 @@ namespace Titanium.Web.Proxy ...@@ -659,13 +371,14 @@ namespace Titanium.Web.Proxy
args.CustomUpStreamProxyUsed = customUpStreamProxy; args.CustomUpStreamProxyUsed = customUpStreamProxy;
return await tcpConnectionFactory.CreateClient(this, return await tcpConnectionFactory.CreateClient(
args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Host,
args.WebSession.Request.RequestUri.Port, args.WebSession.Request.RequestUri.Port,
args.WebSession.Request.HttpVersion, args.WebSession.ConnectRequest?.ClientHelloInfo?.GetAlpn(),
isHttps, isConnect, args.WebSession.Request.HttpVersion, isHttps, isConnect,
args.WebSession.UpStreamEndPoint ?? UpStreamEndPoint, this, args.WebSession.UpStreamEndPoint ?? UpStreamEndPoint,
customUpStreamProxy ?? (isHttps ? UpStreamHttpsProxy : UpStreamHttpProxy)); customUpStreamProxy ?? (isHttps ? UpStreamHttpsProxy : UpStreamHttpProxy),
cancellationToken);
} }
/// <summary> /// <summary>
......
using System; using System;
using System.IO;
using System.IO.Compression;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Compression;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions; using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
...@@ -25,8 +22,9 @@ namespace Titanium.Web.Proxy ...@@ -25,8 +22,9 @@ namespace Titanium.Web.Proxy
{ {
try try
{ {
var cancellationToken = args.CancellationTokenSource.Token;
//read response & headers from server //read response & headers from server
await args.WebSession.ReceiveResponse(); await args.WebSession.ReceiveResponse(cancellationToken);
var response = args.WebSession.Response; var response = args.WebSession.Response;
args.ReRequest = false; args.ReRequest = false;
...@@ -59,12 +57,12 @@ namespace Titanium.Web.Proxy ...@@ -59,12 +57,12 @@ namespace Titanium.Web.Proxy
if (response.TerminateResponse || response.Locked) if (response.TerminateResponse || response.Locked)
{ {
await clientStreamWriter.WriteResponseAsync(response); await clientStreamWriter.WriteResponseAsync(response, cancellationToken: cancellationToken);
if (!response.TerminateResponse) if (!response.TerminateResponse)
{ {
//syphon out the response body from server before setting the new body //syphon out the response body from server before setting the new body
await args.SyphonOutBodyAsync(false); await args.SyphonOutBodyAsync(false, cancellationToken);
} }
else else
{ {
...@@ -80,7 +78,7 @@ namespace Titanium.Web.Proxy ...@@ -80,7 +78,7 @@ namespace Titanium.Web.Proxy
if (args.ReRequest) if (args.ReRequest)
{ {
//clear current response //clear current response
await args.ClearResponse(); await args.ClearResponse(cancellationToken);
await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args); await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args);
return; return;
} }
...@@ -90,13 +88,15 @@ namespace Titanium.Web.Proxy ...@@ -90,13 +88,15 @@ namespace Titanium.Web.Proxy
//Write back to client 100-conitinue response if that's what server returned //Write back to client 100-conitinue response if that's what server returned
if (response.Is100Continue) if (response.Is100Continue)
{ {
await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, (int)HttpStatusCode.Continue, "Continue"); await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion,
await clientStreamWriter.WriteLineAsync(); (int)HttpStatusCode.Continue, "Continue", cancellationToken);
await clientStreamWriter.WriteLineAsync(cancellationToken);
} }
else if (response.ExpectationFailed) else if (response.ExpectationFailed)
{ {
await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, (int)HttpStatusCode.ExpectationFailed, "Expectation Failed"); await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion,
await clientStreamWriter.WriteLineAsync(); (int)HttpStatusCode.ExpectationFailed, "Expectation Failed", cancellationToken);
await clientStreamWriter.WriteLineAsync(cancellationToken);
} }
if (!args.IsTransparent) if (!args.IsTransparent)
...@@ -106,18 +106,19 @@ namespace Titanium.Web.Proxy ...@@ -106,18 +106,19 @@ namespace Titanium.Web.Proxy
if (response.IsBodyRead) if (response.IsBodyRead)
{ {
await clientStreamWriter.WriteResponseAsync(response); await clientStreamWriter.WriteResponseAsync(response, cancellationToken: cancellationToken);
} }
else else
{ {
//Write back response status to client //Write back response status to client
await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, response.StatusCode, response.StatusDescription); await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, response.StatusCode,
await clientStreamWriter.WriteHeadersAsync(response.Headers); response.StatusDescription, cancellationToken);
await clientStreamWriter.WriteHeadersAsync(response.Headers, cancellationToken: cancellationToken);
//Write body if exists //Write body if exists
if (response.HasBody) if (response.HasBody)
{ {
await args.CopyResponseBodyAsync(clientStreamWriter, TransformationMode.None); await args.CopyResponseBodyAsync(clientStreamWriter, TransformationMode.None, cancellationToken);
} }
} }
} }
......
...@@ -14,8 +14,9 @@ namespace Titanium.Web.Proxy.Shared ...@@ -14,8 +14,9 @@ namespace Titanium.Web.Proxy.Shared
internal static readonly char[] SemiColonSplit = { ';' }; internal static readonly char[] SemiColonSplit = { ';' };
internal static readonly char[] EqualSplit = { '=' }; internal static readonly char[] EqualSplit = { '=' };
internal static readonly byte[] NewLine = {(byte)'\r', (byte)'\n' }; internal static readonly byte[] NewLine = { (byte)'\r', (byte)'\n' };
public static readonly Regex CNRemoverRegex = new Regex(@"^CN\s*=\s*", RegexOptions.IgnoreCase | RegexOptions.Compiled); public static readonly Regex CNRemoverRegex =
new Regex(@"^CN\s*=\s*", RegexOptions.IgnoreCase | RegexOptions.Compiled);
} }
} }
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.8.1.4" /> <PackageReference Include="Portable.BouncyCastle" Version="1.8.2" />
<PackageReference Include="StreamExtended" Version="1.0.141-beta" /> <PackageReference Include="StreamExtended" Version="1.0.147-beta" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
...@@ -25,4 +25,17 @@ ...@@ -25,4 +25,17 @@
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<PackageReference Include="Microsoft.Win32.Registry">
<Version>4.4.0</Version>
</PackageReference>
<PackageReference Include="System.Security.Principal.Windows">
<Version>4.4.1</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Reference Include="System.Web" />
</ItemGroup>
</Project> </Project>
\ No newline at end of file
...@@ -14,8 +14,8 @@ ...@@ -14,8 +14,8 @@
<copyright>Copyright &#x00A9; Titanium. All rights reserved.</copyright> <copyright>Copyright &#x00A9; Titanium. All rights reserved.</copyright>
<tags></tags> <tags></tags>
<dependencies> <dependencies>
<dependency id="StreamExtended" version="1.0.141-beta" /> <dependency id="StreamExtended" version="1.0.147-beta" />
<dependency id="Portable.BouncyCastle" version="1.8.1.4" /> <dependency id="Portable.BouncyCastle" version="1.8.2" />
</dependencies> </dependencies>
</metadata> </metadata>
<files> <files>
......
using System;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended;
using StreamExtended.Helpers;
using StreamExtended.Network;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy
{
partial class ProxyServer
{
/// <summary>
/// This is called when this proxy acts as a reverse proxy (like a real http server)
/// So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
/// </summary>
/// <param name="endPoint"></param>
/// <param name="tcpClient"></param>
/// <returns></returns>
private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient)
{
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
try
{
var clientHelloInfo = await SslTools.PeekClientHello(clientStream, cancellationToken);
bool isHttps = clientHelloInfo != null;
string httpsHostName = null;
if (isHttps)
{
httpsHostName = clientHelloInfo.GetServerName() ?? endPoint.GenericCertificateName;
var args = new BeforeSslAuthenticateEventArgs(cancellationTokenSource);
args.SniHostName = httpsHostName;
await endPoint.InvokeBeforeSslAuthenticate(this, args, ExceptionFunc);
if (cancellationTokenSource.IsCancellationRequested)
{
throw new Exception("Session was terminated by user.");
}
if (endPoint.DecryptSsl && args.DecryptSsl)
{
SslStream sslStream = null;
try
{
sslStream = new SslStream(clientStream);
string certName = HttpHelper.GetWildCardDomainName(httpsHostName);
var certificate = await CertificateManager.CreateCertificateAsync(certName);
//Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls, false);
//HTTPS server created - we can now decrypt the client's traffic
clientStream = new CustomBufferedStream(sslStream, BufferSize);
clientStreamReader.Dispose();
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
}
catch (Exception e)
{
ExceptionFunc(new Exception(
$"Could'nt authenticate client '{httpsHostName}' with fake certificate.", e));
sslStream?.Dispose();
return;
}
}
else
{
//create new connection
var connection = new TcpClient(UpStreamEndPoint);
await connection.ConnectAsync(httpsHostName, endPoint.Port);
connection.ReceiveTimeout = ConnectionTimeOutSeconds * 1000;
connection.SendTimeout = ConnectionTimeOutSeconds * 1000;
using (connection)
{
var serverStream = connection.GetStream();
int available = clientStream.Available;
if (available > 0)
{
//send the buffered data
var data = BufferPool.GetBuffer(BufferSize);
try
{
// clientStream.Available sbould be at most BufferSize because it is using the same buffer size
await clientStream.ReadAsync(data, 0, available, cancellationToken);
await serverStream.WriteAsync(data, 0, available, cancellationToken);
await serverStream.FlushAsync(cancellationToken);
}
finally
{
BufferPool.ReturnBuffer(data);
}
}
//var serverHelloInfo = await SslTools.PeekServerHello(serverStream);
await TcpHelper.SendRaw(clientStream, serverStream, BufferSize,
null, null, cancellationTokenSource, ExceptionFunc);
}
}
}
//HTTPS server created - we can now decrypt the client's traffic
//Now create the request
await HandleHttpSessionRequest(endPoint, tcpClient, clientStream, clientStreamReader,
clientStreamWriter, cancellationTokenSource, isHttps ? httpsHostName : null, null, true);
}
finally
{
clientStreamReader.Dispose();
clientStream.Dispose();
if (!cancellationTokenSource.IsCancellationRequested)
{
cancellationTokenSource.Cancel();
}
}
}
}
}
...@@ -24,11 +24,11 @@ namespace Titanium.Web.Proxy ...@@ -24,11 +24,11 @@ namespace Titanium.Web.Proxy
"KerberosAuthorization" "KerberosAuthorization"
}; };
private static readonly HashSet<string> authSchemes = new HashSet<string> private static readonly HashSet<string> authSchemes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{ {
"NTLM".ToLower(), "NTLM",
"Negotiate".ToLower(), "Negotiate",
"Kerberos".ToLower() "Kerberos"
}; };
/// <summary> /// <summary>
...@@ -58,7 +58,8 @@ namespace Titanium.Web.Proxy ...@@ -58,7 +58,8 @@ namespace Titanium.Web.Proxy
if (headerName != null) if (headerName != null)
{ {
authHeader = response.Headers.NonUniqueHeaders[headerName] authHeader = response.Headers.NonUniqueHeaders[headerName]
.FirstOrDefault(x => authSchemes.Any(y => x.Value.StartsWith(y, StringComparison.OrdinalIgnoreCase))); .FirstOrDefault(
x => authSchemes.Any(y => x.Value.StartsWith(y, StringComparison.OrdinalIgnoreCase)));
} }
//check in unique headers //check in unique headers
...@@ -85,9 +86,10 @@ namespace Titanium.Web.Proxy ...@@ -85,9 +86,10 @@ namespace Titanium.Web.Proxy
if (authHeader != null) if (authHeader != null)
{ {
string scheme = authSchemes.Contains(authHeader.Value.ToLower()) ? authHeader.Value.ToLower() : null; string scheme = authSchemes.Contains(authHeader.Value) ? authHeader.Value : null;
var expectedAuthState = scheme == null ? State.WinAuthState.INITIAL_TOKEN : State.WinAuthState.UNAUTHORIZED; var expectedAuthState =
scheme == null ? State.WinAuthState.INITIAL_TOKEN : State.WinAuthState.UNAUTHORIZED;
if (!WinAuthEndPoint.ValidateWinAuthState(args.WebSession.RequestId, expectedAuthState)) if (!WinAuthEndPoint.ValidateWinAuthState(args.WebSession.RequestId, expectedAuthState))
{ {
...@@ -120,7 +122,8 @@ namespace Titanium.Web.Proxy ...@@ -120,7 +122,8 @@ namespace Titanium.Web.Proxy
//challenge value will start with any of the scheme selected //challenge value will start with any of the scheme selected
else else
{ {
scheme = authSchemes.First(x => authHeader.Value.StartsWith(x, StringComparison.OrdinalIgnoreCase) && scheme = authSchemes.First(x =>
authHeader.Value.StartsWith(x, StringComparison.OrdinalIgnoreCase) &&
authHeader.Value.Length > x.Length + 1); authHeader.Value.Length > x.Length + 1);
string serverToken = authHeader.Value.Substring(scheme.Length + 1); string serverToken = authHeader.Value.Substring(scheme.Length + 1);
...@@ -157,27 +160,30 @@ namespace Titanium.Web.Proxy ...@@ -157,27 +160,30 @@ namespace Titanium.Web.Proxy
var response = args.WebSession.Response; var response = args.WebSession.Response;
// Strip authentication headers to avoid credentials prompt in client web browser // Strip authentication headers to avoid credentials prompt in client web browser
foreach (var authHeaderName in authHeaderNames) foreach (string authHeaderName in authHeaderNames)
{ {
response.Headers.RemoveHeader(authHeaderName); response.Headers.RemoveHeader(authHeaderName);
} }
// Add custom div to body to clarify that the proxy (not the client browser) failed authentication // Add custom div to body to clarify that the proxy (not the client browser) failed authentication
string authErrorMessage = "<div class=\"inserted-by-proxy\"><h2>NTLM authentication through Titanium.Web.Proxy (" + string authErrorMessage =
"<div class=\"inserted-by-proxy\"><h2>NTLM authentication through Titanium.Web.Proxy (" +
args.ProxyClient.TcpClient.Client.LocalEndPoint + args.ProxyClient.TcpClient.Client.LocalEndPoint +
") failed. Please check credentials.</h2></div>"; ") failed. Please check credentials.</h2></div>";
string originalErrorMessage = "<div class=\"inserted-by-proxy\"><h3>Response from remote web server below.</h3></div><br/>"; string originalErrorMessage =
string body = await args.GetResponseBodyAsString(); "<div class=\"inserted-by-proxy\"><h3>Response from remote web server below.</h3></div><br/>";
string body = await args.GetResponseBodyAsString(args.CancellationTokenSource.Token);
int idx = body.IndexOfIgnoreCase("<body>"); int idx = body.IndexOfIgnoreCase("<body>");
if (idx >= 0) if (idx >= 0)
{ {
var bodyPos = idx + "<body>".Length; int bodyPos = idx + "<body>".Length;
body = body.Insert(bodyPos, authErrorMessage + originalErrorMessage); body = body.Insert(bodyPos, authErrorMessage + originalErrorMessage);
} }
else else
{ {
// Cannot parse response body, replace it // Cannot parse response body, replace it
body = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">" + body =
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">" +
"<html xmlns=\"http://www.w3.org/1999/xhtml\">" + "<html xmlns=\"http://www.w3.org/1999/xhtml\">" +
"<body>" + "<body>" +
authErrorMessage + authErrorMessage +
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<runtime> <runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
</assemblyBinding> </assemblyBinding>
</runtime> </runtime>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup> </startup>
</configuration> </configuration>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Portable.BouncyCastle" version="1.8.1.4" targetFramework="net45" /> <package id="Portable.BouncyCastle" version="1.8.2" targetFramework="net45" />
<package id="StreamExtended" version="1.0.141-beta" targetFramework="net45" /> <package id="StreamExtended" version="1.0.147-beta" targetFramework="net45" />
</packages> </packages>
\ No newline at end of file
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