Commit 000f3715 authored by justcoding121's avatar justcoding121

enforce max line length; enforce one line braces

parent f870fad5
...@@ -9,14 +9,15 @@ namespace Titanium.Web.Proxy ...@@ -9,14 +9,15 @@ namespace Titanium.Web.Proxy
public partial class ProxyServer public partial class ProxyServer
{ {
/// <summary> /// <summary>
/// Call back to override server certificate validation /// Call back to override server certificate validation
/// </summary> /// </summary>
/// <param name="sender"></param> /// <param name="sender"></param>
/// <param name="certificate"></param> /// <param name="certificate"></param>
/// <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)
...@@ -44,7 +45,7 @@ namespace Titanium.Web.Proxy ...@@ -44,7 +45,7 @@ namespace Titanium.Web.Proxy
} }
/// <summary> /// <summary>
/// Call back to select client certificate used for mutual authentication /// Call back to select client certificate used for mutual authentication
/// </summary> /// </summary>
/// <param name="sender"></param> /// <param name="sender"></param>
/// <param name="targetHost"></param> /// <param name="targetHost"></param>
...@@ -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;
......
...@@ -4,7 +4,7 @@ using Titanium.Web.Proxy.Http; ...@@ -4,7 +4,7 @@ using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Compression namespace Titanium.Web.Proxy.Compression
{ {
/// <summary> /// <summary>
/// A factory to generate the compression methods based on the type of compression /// A factory to generate the compression methods based on the type of compression
/// </summary> /// </summary>
internal static class CompressionFactory internal static class CompressionFactory
{ {
......
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression namespace Titanium.Web.Proxy.Compression
{ {
/// <summary> /// <summary>
/// Concrete implementation of deflate compression /// Concrete implementation of deflate compression
/// </summary> /// </summary>
internal class DeflateCompression : ICompression internal class DeflateCompression : ICompression
{ {
......
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression namespace Titanium.Web.Proxy.Compression
{ {
/// <summary> /// <summary>
/// concreate implementation of gzip compression /// concreate implementation of gzip compression
/// </summary> /// </summary>
internal class GZipCompression : ICompression internal class GZipCompression : ICompression
{ {
......
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);
} }
......
...@@ -4,13 +4,15 @@ using Titanium.Web.Proxy.Http; ...@@ -4,13 +4,15 @@ using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
/// <summary> /// <summary>
/// A factory to generate the de-compression methods based on the type of compression /// A factory to generate the de-compression methods based on the type of compression
/// </summary> /// </summary>
internal class DecompressionFactory internal class DecompressionFactory
{ {
//cache //cache
private static readonly Lazy<IDecompression> gzip = new Lazy<IDecompression>(() => new GZipDecompression()); private static readonly Lazy<IDecompression> gzip = new Lazy<IDecompression>(() => new GZipDecompression());
private static readonly Lazy<IDecompression> deflate = new Lazy<IDecompression>(() => new DeflateDecompression());
private static readonly Lazy<IDecompression> deflate =
new Lazy<IDecompression>(() => new DeflateDecompression());
public static IDecompression Create(string type) public static IDecompression Create(string 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
{ {
/// <summary> /// <summary>
/// concrete implementation of deflate de-compression /// concrete implementation of deflate de-compression
/// </summary> /// </summary>
internal class DeflateDecompression : IDecompression internal class DeflateDecompression : IDecompression
{ {
......
using StreamExtended.Helpers; using System.IO;
using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
/// <summary> /// <summary>
/// concrete implementation of gzip de-compression /// concrete implementation of gzip de-compression
/// </summary> /// </summary>
internal class GZipDecompression : IDecompression internal class GZipDecompression : IDecompression
{ {
......
using System.IO; using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
/// <summary> /// <summary>
/// An interface for decompression /// An interface for decompression
/// </summary> /// </summary>
internal interface IDecompression internal interface IDecompression
{ {
......
...@@ -3,4 +3,4 @@ ...@@ -3,4 +3,4 @@
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
public delegate Task AsyncEventHandler<TEventArgs>(object sender, TEventArgs e); public delegate Task AsyncEventHandler<TEventArgs>(object sender, TEventArgs e);
} }
\ No newline at end of file
...@@ -4,37 +4,37 @@ using System.Security.Cryptography.X509Certificates; ...@@ -4,37 +4,37 @@ using System.Security.Cryptography.X509Certificates;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
/// <summary> /// <summary>
/// An argument passed on to user for client certificate selection during mutual SSL authentication /// An argument passed on to user for client certificate selection during mutual SSL authentication
/// </summary> /// </summary>
public class CertificateSelectionEventArgs : EventArgs public class CertificateSelectionEventArgs : EventArgs
{ {
/// <summary> /// <summary>
/// Sender object. /// Sender object.
/// </summary> /// </summary>
public object Sender { get; internal set; } public object Sender { get; internal set; }
/// <summary> /// <summary>
/// Target host. /// Target host.
/// </summary> /// </summary>
public string TargetHost { get; internal set; } public string TargetHost { get; internal set; }
/// <summary> /// <summary>
/// Local certificates. /// Local certificates.
/// </summary> /// </summary>
public X509CertificateCollection LocalCertificates { get; internal set; } public X509CertificateCollection LocalCertificates { get; internal set; }
/// <summary> /// <summary>
/// Remote certificate. /// Remote certificate.
/// </summary> /// </summary>
public X509Certificate RemoteCertificate { get; internal set; } public X509Certificate RemoteCertificate { get; internal set; }
/// <summary> /// <summary>
/// Acceptable issuers. /// Acceptable issuers.
/// </summary> /// </summary>
public string[] AcceptableIssuers { get; internal set; } public string[] AcceptableIssuers { get; internal set; }
/// <summary> /// <summary>
/// Client Certificate. /// Client Certificate.
/// </summary> /// </summary>
public X509Certificate ClientCertificate { get; set; } public X509Certificate ClientCertificate { get; set; }
} }
......
...@@ -5,27 +5,27 @@ using System.Security.Cryptography.X509Certificates; ...@@ -5,27 +5,27 @@ using System.Security.Cryptography.X509Certificates;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
/// <summary> /// <summary>
/// An argument passed on to the user for validating the server certificate during SSL authentication /// An argument passed on to the user for validating the server certificate during SSL authentication
/// </summary> /// </summary>
public class CertificateValidationEventArgs : EventArgs public class CertificateValidationEventArgs : EventArgs
{ {
/// <summary> /// <summary>
/// Certificate /// Certificate
/// </summary> /// </summary>
public X509Certificate Certificate { get; internal set; } public X509Certificate Certificate { get; internal set; }
/// <summary> /// <summary>
/// Certificate chain /// Certificate chain
/// </summary> /// </summary>
public X509Chain Chain { get; internal set; } public X509Chain Chain { get; internal set; }
/// <summary> /// <summary>
/// SSL policy errors. /// SSL policy errors.
/// </summary> /// </summary>
public SslPolicyErrors SslPolicyErrors { get; internal set; } public SslPolicyErrors SslPolicyErrors { get; internal set; }
/// <summary> /// <summary>
/// is a valid certificate? /// is a valid certificate?
/// </summary> /// </summary>
public bool IsValid { get; set; } public bool IsValid { get; set; }
} }
......
...@@ -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; }
public int Offset { get; }
public int Count { get; }
internal DataEventArgs(byte[] buffer, int offset, int count) internal 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; }
} }
} }
\ No newline at end of file
...@@ -9,25 +9,40 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -9,25 +9,40 @@ 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;
internal 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;
this.isChunked = isChunked; this.isChunked = isChunked;
bytesRemaining = isChunked bytesRemaining = isChunked
? 0 ? 0
: contentLength == -1 : contentLength == -1
? long.MaxValue ? long.MaxValue
: 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)
...@@ -42,7 +57,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -42,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);
} }
...@@ -133,19 +147,5 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -133,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; }
} }
} }
\ No newline at end of file
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended.Helpers; using StreamExtended.Helpers;
using StreamExtended.Network; using StreamExtended.Network;
...@@ -10,8 +11,6 @@ using Titanium.Web.Proxy.Helpers; ...@@ -10,8 +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;
using System.Threading;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
...@@ -30,6 +29,21 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -30,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, cancellationTokenSource, null, exceptionFunc)
{
}
protected SessionEventArgs(int bufferSize, ProxyEndPoint endPoint,
CancellationTokenSource cancellationTokenSource, Request request, ExceptionHandler exceptionFunc)
: base(bufferSize, endPoint, cancellationTokenSource, request, exceptionFunc)
{
}
private bool hasMulipartEventSubscribers => MultipartRequestPartSent != null; private bool hasMulipartEventSubscribers => MultipartRequestPartSent != null;
/// <summary> /// <summary>
...@@ -54,21 +68,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -54,21 +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,
CancellationTokenSource cancellationTokenSource, ExceptionHandler exceptionFunc)
: this(bufferSize, endPoint, cancellationTokenSource, null, exceptionFunc)
{
}
protected SessionEventArgs(int bufferSize, ProxyEndPoint endPoint,
CancellationTokenSource cancellationTokenSource, Request request, ExceptionHandler exceptionFunc)
: base(bufferSize, endPoint, cancellationTokenSource, request, exceptionFunc)
{
}
private CustomBufferedStream GetStream(bool isRequest) private CustomBufferedStream GetStream(bool isRequest)
{ {
return isRequest ? ProxyClient.ClientStream : WebSession.ServerConnection.Stream; return isRequest ? ProxyClient.ClientStream : WebSession.ServerConnection.Stream;
...@@ -476,7 +475,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -476,7 +475,7 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
response.Headers.AddHeaders(headers); response.Headers.AddHeaders(headers);
} }
response.HttpVersion = WebSession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.Body = response.Encoding.GetBytes(html ?? string.Empty); response.Body = response.Encoding.GetBytes(html ?? string.Empty);
......
...@@ -9,15 +9,15 @@ using Titanium.Web.Proxy.Network; ...@@ -9,15 +9,15 @@ using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
/// <summary> /// <summary>
/// Holds info related to a single proxy session (single request/response sequence) /// Holds info related to a single proxy session (single request/response sequence)
/// A proxy session is bounded to a single connection from client /// A proxy session is bounded to a single connection from client
/// A proxy session ends when client terminates connection to proxy /// A proxy session ends when client terminates connection to proxy
/// or when server terminates connection from proxy /// or when server terminates connection from proxy
/// </summary> /// </summary>
public class SessionEventArgsBase : EventArgs, IDisposable public class SessionEventArgsBase : EventArgs, IDisposable
{ {
/// <summary> /// <summary>
/// Size of Buffers used by this object /// Size of Buffers used by this object
/// </summary> /// </summary>
protected readonly int BufferSize; protected readonly int BufferSize;
...@@ -26,41 +26,78 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -26,41 +26,78 @@ namespace Titanium.Web.Proxy.EventArguments
internal CancellationTokenSource cancellationTokenSource; internal CancellationTokenSource cancellationTokenSource;
/// <summary> /// <summary>
/// Holds a reference to client /// 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;
this.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))
{
return NetworkHelper.GetProcessIdFromPort(remoteEndPoint.Port, endPoint.IpV6Enabled);
}
//can't access process Id of remote request from remote machine
return -1;
}
throw new PlatformNotSupportedException();
});
}
/// <summary>
/// Holds a reference to client
/// </summary> /// </summary>
internal ProxyClient ProxyClient { get; } internal ProxyClient ProxyClient { get; }
/// <summary> /// <summary>
/// Returns a unique Id for this request/response session /// Returns a unique Id for this request/response session
/// same as RequestId of WebSession /// same as RequestId of WebSession
/// </summary> /// </summary>
public Guid Id => WebSession.RequestId; public Guid Id => WebSession.RequestId;
/// <summary> /// <summary>
/// Does this session uses SSL /// Does this session uses SSL
/// </summary> /// </summary>
public bool IsHttps => WebSession.Request.IsHttps; public bool IsHttps => WebSession.Request.IsHttps;
/// <summary> /// <summary>
/// Client End Point. /// Client End Point.
/// </summary> /// </summary>
public IPEndPoint ClientEndPoint => (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint; public IPEndPoint ClientEndPoint => (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint;
/// <summary> /// <summary>
/// A web session corresponding to a single request/response sequence /// A web session corresponding to a single request/response sequence
/// within a proxy connection /// within a proxy connection
/// </summary> /// </summary>
public HttpWebClient WebSession { get; } public HttpWebClient WebSession { get; }
/// <summary> /// <summary>
/// Are we using a custom upstream HTTP(S) proxy? /// Are we using a custom upstream HTTP(S) proxy?
/// </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;
...@@ -68,45 +105,22 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -68,45 +105,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, public virtual void Dispose()
CancellationTokenSource cancellationTokenSource, ExceptionHandler exceptionFunc)
: this(bufferSize, endPoint, cancellationTokenSource, null, exceptionFunc)
{
}
protected SessionEventArgsBase(int bufferSize, ProxyEndPoint endPoint,
CancellationTokenSource cancellationTokenSource,
Request request, ExceptionHandler exceptionFunc)
{ {
this.BufferSize = bufferSize; CustomUpStreamProxyUsed = null;
this.ExceptionFunc = exceptionFunc;
this.cancellationTokenSource = cancellationTokenSource;
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)
{ {
...@@ -133,24 +147,11 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -133,24 +147,11 @@ namespace Titanium.Web.Proxy.EventArguments
} }
/// <summary> /// <summary>
/// Terminates the session abruptly by terminating client/server connections /// Terminates the session abruptly by terminating client/server connections
/// </summary> /// </summary>
public void TerminateSession() public void TerminateSession()
{ {
cancellationTokenSource.Cancel(); cancellationTokenSource.Cancel();
} }
/// <summary>
/// implement any cleanup here
/// </summary>
public virtual void Dispose()
{
CustomUpStreamProxyUsed = null;
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
{ {
None, None,
/// <summary> /// <summary>
/// Removes the chunked encoding /// Removes the chunked encoding
/// </summary> /// </summary>
RemoveChunked, RemoveChunked,
/// <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.Threading;
using System.Threading;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
...@@ -7,18 +6,19 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -7,18 +6,19 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
public class TunnelConnectSessionEventArgs : SessionEventArgsBase public class TunnelConnectSessionEventArgs : SessionEventArgsBase
{ {
internal TunnelConnectSessionEventArgs(int bufferSize, ProxyEndPoint endPoint, ConnectRequest connectRequest,
ExceptionHandler exceptionFunc, CancellationTokenSource cancellationTokenSource)
: base(bufferSize, endPoint, cancellationTokenSource, connectRequest, exceptionFunc)
{
}
public bool DecryptSsl { get; set; } = true; public bool DecryptSsl { get; set; } = true;
/// <summary> /// <summary>
/// Denies the connect request with a Forbidden status /// Denies the connect request with a Forbidden status
/// </summary> /// </summary>
public bool DenyConnect { get; set; } public bool DenyConnect { get; set; }
public bool IsHttpsConnect { get; internal set; } public bool IsHttpsConnect { get; internal set; }
internal TunnelConnectSessionEventArgs(int bufferSize, ProxyEndPoint endPoint, ConnectRequest connectRequest, ExceptionHandler exceptionFunc, CancellationTokenSource cancellationTokenSource)
: base(bufferSize, endPoint, cancellationTokenSource, connectRequest, exceptionFunc)
{
}
} }
} }
...@@ -3,4 +3,4 @@ using System; ...@@ -3,4 +3,4 @@ using System;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
public delegate void ExceptionHandler(Exception exception); public delegate void ExceptionHandler(Exception exception);
} }
\ No newline at end of file
namespace Titanium.Web.Proxy.Exceptions namespace Titanium.Web.Proxy.Exceptions
{ {
/// <summary> /// <summary>
/// An expception thrown when body is unexpectedly empty /// An expception thrown when body is unexpectedly empty
/// </summary> /// </summary>
public class BodyNotFoundException : ProxyException public class BodyNotFoundException : ProxyException
{ {
/// <summary> /// <summary>
/// Constructor. /// Constructor.
/// </summary> /// </summary>
/// <param name="message"></param> /// <param name="message"></param>
internal BodyNotFoundException(string message) : base(message) internal BodyNotFoundException(string message) : base(message)
......
...@@ -6,18 +6,19 @@ using Titanium.Web.Proxy.Models; ...@@ -6,18 +6,19 @@ using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Exceptions namespace Titanium.Web.Proxy.Exceptions
{ {
/// <summary> /// <summary>
/// Proxy authorization exception /// Proxy authorization exception
/// </summary> /// </summary>
public class ProxyAuthorizationException : ProxyException public class ProxyAuthorizationException : ProxyException
{ {
/// <summary> /// <summary>
/// Instantiate new instance /// Instantiate new instance
/// </summary> /// </summary>
/// <param name="message">Exception message</param> /// <param name="message">Exception message</param>
/// <param name="session">The <see cref="SessionEventArgs"/> instance containing the event data.</param> /// <param name="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;
...@@ -26,7 +27,7 @@ namespace Titanium.Web.Proxy.Exceptions ...@@ -26,7 +27,7 @@ namespace Titanium.Web.Proxy.Exceptions
public SessionEventArgsBase Session { get; } public SessionEventArgsBase Session { get; }
/// <summary> /// <summary>
/// Headers associated with the authorization exception /// Headers associated with the authorization exception
/// </summary> /// </summary>
public IEnumerable<HttpHeader> Headers { get; } public IEnumerable<HttpHeader> Headers { get; }
} }
......
...@@ -3,12 +3,12 @@ ...@@ -3,12 +3,12 @@
namespace Titanium.Web.Proxy.Exceptions namespace Titanium.Web.Proxy.Exceptions
{ {
/// <summary> /// <summary>
/// Base class exception associated with this proxy implementation /// Base class exception associated with this proxy implementation
/// </summary> /// </summary>
public abstract class ProxyException : Exception public abstract class ProxyException : Exception
{ {
/// <summary> /// <summary>
/// Instantiate a new instance of this exception - must be invoked by derived classes' constructors /// Instantiate a new instance of this exception - must be invoked by derived classes' constructors
/// </summary> /// </summary>
/// <param name="message">Exception message</param> /// <param name="message">Exception message</param>
protected ProxyException(string message) : base(message) protected ProxyException(string message) : base(message)
...@@ -16,7 +16,7 @@ namespace Titanium.Web.Proxy.Exceptions ...@@ -16,7 +16,7 @@ namespace Titanium.Web.Proxy.Exceptions
} }
/// <summary> /// <summary>
/// Instantiate this exception - must be invoked by derived classes' constructors /// Instantiate this exception - must be invoked by derived classes' constructors
/// </summary> /// </summary>
/// <param name="message">Excception message</param> /// <param name="message">Excception message</param>
/// <param name="innerException">Inner exception associated</param> /// <param name="innerException">Inner exception associated</param>
......
...@@ -4,26 +4,27 @@ using Titanium.Web.Proxy.EventArguments; ...@@ -4,26 +4,27 @@ using Titanium.Web.Proxy.EventArguments;
namespace Titanium.Web.Proxy.Exceptions namespace Titanium.Web.Proxy.Exceptions
{ {
/// <summary> /// <summary>
/// Proxy HTTP exception /// Proxy HTTP exception
/// </summary> /// </summary>
public class ProxyHttpException : ProxyException public class ProxyHttpException : ProxyException
{ {
/// <summary> /// <summary>
/// Instantiate new instance /// Instantiate new instance
/// </summary> /// </summary>
/// <param name="message">Message for this exception</param> /// <param name="message">Message for this exception</param>
/// <param name="innerException">Associated inner exception</param> /// <param name="innerException">Associated inner exception</param>
/// <param name="sessionEventArgs">Instance of <see cref="EventArguments.SessionEventArgs"/> associated to the exception</param> /// <param name="sessionEventArgs">Instance of <see cref="EventArguments.SessionEventArgs" /> associated to the exception</param>
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;
} }
/// <summary> /// <summary>
/// Gets session info associated to the exception /// Gets session info associated to the exception
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This object should not be edited /// This object should not be edited
/// </remarks> /// </remarks>
public SessionEventArgs SessionEventArgs { get; } public SessionEventArgs SessionEventArgs { get; }
} }
......
...@@ -3,6 +3,7 @@ using System.IO; ...@@ -3,6 +3,7 @@ using System.IO;
using System.Net; using System.Net;
using System.Net.Security; using System.Net.Security;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended; using StreamExtended;
using StreamExtended.Helpers; using StreamExtended.Helpers;
...@@ -12,15 +13,14 @@ using Titanium.Web.Proxy.Exceptions; ...@@ -12,15 +13,14 @@ using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using System.Threading;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
partial class ProxyServer partial class ProxyServer
{ {
/// <summary> /// <summary>
/// This is called when client is aware of proxy /// 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 /// So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
/// <param name="tcpClient"></param> /// <param name="tcpClient"></param>
...@@ -57,12 +57,13 @@ namespace Titanium.Web.Proxy ...@@ -57,12 +57,13 @@ namespace Titanium.Web.Proxy
{ {
RequestUri = httpRemoteUri, RequestUri = httpRemoteUri,
OriginalUrl = httpUrl, OriginalUrl = httpUrl,
HttpVersion = version, HttpVersion = version
}; };
await HeaderParser.ReadHeaders(clientStreamReader, connectRequest.Headers); await HeaderParser.ReadHeaders(clientStreamReader, connectRequest.Headers);
var connectArgs = new TunnelConnectSessionEventArgs(BufferSize, endPoint, connectRequest, ExceptionFunc, cancellationTokenSource); var connectArgs = new TunnelConnectSessionEventArgs(BufferSize, endPoint, connectRequest,
ExceptionFunc, cancellationTokenSource);
connectArgs.ProxyClient.TcpClient = tcpClient; connectArgs.ProxyClient.TcpClient = tcpClient;
connectArgs.ProxyClient.ClientStream = clientStream; connectArgs.ProxyClient.ClientStream = clientStream;
...@@ -79,7 +80,7 @@ namespace Titanium.Web.Proxy ...@@ -79,7 +80,7 @@ namespace Titanium.Web.Proxy
{ {
HttpVersion = HttpHeader.Version11, HttpVersion = HttpHeader.Version11,
StatusCode = (int)HttpStatusCode.Forbidden, StatusCode = (int)HttpStatusCode.Forbidden,
StatusDescription = "Forbidden", StatusDescription = "Forbidden"
}; };
} }
...@@ -128,7 +129,8 @@ namespace Titanium.Web.Proxy ...@@ -128,7 +129,8 @@ namespace Titanium.Web.Proxy
string certName = HttpHelper.GetWildCardDomainName(connectHostname); string certName = HttpHelper.GetWildCardDomainName(connectHostname);
var certificate = endPoint.GenericCertificate ?? await CertificateManager.CreateCertificateAsync(certName); var certificate = endPoint.GenericCertificate ??
await CertificateManager.CreateCertificateAsync(certName);
//Successfully managed to authenticate the client using the fake certificate //Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false, SupportedSslProtocols, false); await sslStream.AuthenticateAsServerAsync(certificate, false, SupportedSslProtocols, false);
...@@ -142,14 +144,14 @@ namespace Titanium.Web.Proxy ...@@ -142,14 +144,14 @@ namespace Titanium.Web.Proxy
} }
catch (Exception e) catch (Exception e)
{ {
ExceptionFunc(new Exception($"Could'nt authenticate client '{connectHostname}' with fake certificate.", e)); ExceptionFunc(new Exception(
$"Could'nt authenticate client '{connectHostname}' with fake certificate.", e));
sslStream?.Dispose(); sslStream?.Dispose();
return; return;
} }
if (await HttpHelper.IsConnectMethod(clientStream) == -1) if (await HttpHelper.IsConnectMethod(clientStream) == -1)
{ {
// It can be for example some Google (Cloude Messaging for Chrome) magic
decryptSsl = false; decryptSsl = false;
} }
} }
...@@ -200,7 +202,8 @@ namespace Titanium.Web.Proxy ...@@ -200,7 +202,8 @@ namespace Titanium.Web.Proxy
} }
//Now create the request //Now create the request
await HandleHttpSessionRequest(endPoint, tcpClient, clientStream, clientStreamReader, clientStreamWriter, cancellationTokenSource, connectHostname, connectRequest); await HandleHttpSessionRequest(endPoint, tcpClient, clientStream, clientStreamReader,
clientStreamWriter, cancellationTokenSource, connectHostname, connectRequest);
} }
catch (ProxyHttpException e) catch (ProxyHttpException e)
{ {
...@@ -228,7 +231,5 @@ namespace Titanium.Web.Proxy ...@@ -228,7 +231,5 @@ namespace Titanium.Web.Proxy
} }
} }
} }
} }
} }
...@@ -3,12 +3,12 @@ ...@@ -3,12 +3,12 @@
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
/// <summary> /// <summary>
/// Extension methods for Byte Arrays. /// Extension methods for Byte Arrays.
/// </summary> /// </summary>
internal static class ByteArrayExtensions internal static class ByteArrayExtensions
{ {
/// <summary> /// <summary>
/// Get the sub array from byte of data /// Get the sub array from byte of data
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <param name="data"></param> /// <param name="data"></param>
......
...@@ -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 StreamExtended;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StreamExtended;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
static class SslExtensions internal static class SslExtensions
{ {
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;
} }
......
...@@ -7,40 +7,43 @@ using StreamExtended.Helpers; ...@@ -7,40 +7,43 @@ using StreamExtended.Helpers;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
/// <summary> /// <summary>
/// Extensions used for Stream and CustomBinaryReader objects /// Extensions used for Stream and CustomBinaryReader objects
/// </summary> /// </summary>
internal static class StreamExtensions internal static class StreamExtensions
{ {
/// <summary> /// <summary>
/// Copy streams asynchronously /// Copy streams asynchronously
/// </summary> /// </summary>
/// <param name="input"></param> /// <param name="input"></param>
/// <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);
} }
/// <summary> /// <summary>
/// Copy streams asynchronously /// Copy streams asynchronously
/// </summary> /// </summary>
/// <param name="input"></param> /// <param name="input"></param>
/// <param name="output"></param> /// <param name="output"></param>
/// <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;
} }
} }
......
...@@ -17,13 +17,14 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -17,13 +17,14 @@ 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> /// <summary>
/// Gets the local port from a native TCP row object. /// Gets the local port from a native TCP row object.
/// </summary> /// </summary>
/// <param name="tcpRow">The TCP row.</param> /// <param name="tcpRow">The TCP row.</param>
/// <returns>The local port</returns> /// <returns>The local port</returns>
...@@ -33,13 +34,14 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -33,13 +34,14 @@ namespace Titanium.Web.Proxy.Extensions
} }
/// <summary> /// <summary>
/// Gets the remote port from a native TCP row object. /// Gets the remote port from a native TCP row object.
/// </summary> /// </summary>
/// <param name="tcpRow">The TCP row.</param> /// <param name="tcpRow">The TCP row.</param>
/// <returns>The remote port</returns> /// <returns>The remote port</returns>
internal static int GetRemotePort(this NativeMethods.TcpRow tcpRow) internal static int GetRemotePort(this NativeMethods.TcpRow tcpRow)
{ {
return (tcpRow.remotePort1 << 8) + tcpRow.remotePort2 + (tcpRow.remotePort3 << 24) + (tcpRow.remotePort4 << 16); 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)
......
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;
...@@ -13,7 +13,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -13,7 +13,7 @@ namespace Titanium.Web.Proxy.Helpers
private static readonly Encoding defaultEncoding = Encoding.GetEncoding("ISO-8859-1"); private static readonly Encoding defaultEncoding = Encoding.GetEncoding("ISO-8859-1");
/// <summary> /// <summary>
/// Gets the character encoding of request/response from content-type header /// Gets the character encoding of request/response from content-type header
/// </summary> /// </summary>
/// <param name="contentType"></param> /// <param name="contentType"></param>
/// <returns></returns> /// <returns></returns>
...@@ -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;
} }
...@@ -87,9 +86,9 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -87,9 +86,9 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// Tries to get root domain from a given hostname /// Tries to get root domain from a given hostname
/// Adapted from below answer /// Adapted from below answer
/// https://stackoverflow.com/questions/16473838/get-domain-name-of-a-url-in-c-sharp-net /// https://stackoverflow.com/questions/16473838/get-domain-name-of-a-url-in-c-sharp-net
/// </summary> /// </summary>
/// <param name="hostname"></param> /// <param name="hostname"></param>
/// <returns></returns> /// <returns></returns>
...@@ -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;
} }
...@@ -117,7 +116,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -117,7 +116,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// Determines whether is connect method. /// Determines whether is connect method.
/// </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>
...@@ -135,14 +134,12 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -135,14 +134,12 @@ namespace Titanium.Web.Proxy.Helpers
if (b == ' ' && i > 2) if (b == ' ' && i > 2)
{ {
// at least 3 letters and a space
return isConnect ? 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;
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
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)
{ {
......
...@@ -5,14 +5,14 @@ using Titanium.Web.Proxy.Http; ...@@ -5,14 +5,14 @@ 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)
{ {
} }
/// <summary> /// <summary>
/// Writes the response. /// Writes the response.
/// </summary> /// </summary>
/// <param name="response"></param> /// <param name="response"></param>
/// <param name="flush"></param> /// <param name="flush"></param>
...@@ -24,7 +24,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -24,7 +24,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// Write response status /// Write response status
/// </summary> /// </summary>
/// <param name="version"></param> /// <param name="version"></param>
/// <param name="code"></param> /// <param name="code"></param>
......
...@@ -5,7 +5,6 @@ using System.Text; ...@@ -5,7 +5,6 @@ using System.Text;
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 +12,12 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -13,14 +12,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,6 +26,8 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -29,6 +26,8 @@ namespace Titanium.Web.Proxy.Helpers
charBuffer = new char[BufferSize - 1]; charBuffer = new char[BufferSize - 1];
} }
public int BufferSize { get; }
public Task WriteLineAsync() public Task WriteLineAsync()
{ {
return WriteAsync(newLine); return WriteAsync(newLine);
...@@ -87,7 +86,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -87,7 +86,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// Write the headers to client /// Write the headers to client
/// </summary> /// </summary>
/// <param name="headers"></param> /// <param name="headers"></param>
/// <param name="flush"></param> /// <param name="flush"></param>
...@@ -102,7 +101,6 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -102,7 +101,6 @@ namespace Titanium.Web.Proxy.Helpers
await WriteLineAsync(); await WriteLineAsync();
if (flush) if (flush)
{ {
// flush the stream
await FlushAsync(); await FlushAsync();
} }
} }
...@@ -112,7 +110,6 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -112,7 +110,6 @@ namespace Titanium.Web.Proxy.Helpers
await WriteAsync(data, 0, data.Length); await WriteAsync(data, 0, data.Length);
if (flush) if (flush)
{ {
// flush the stream
await FlushAsync(); await FlushAsync();
} }
} }
...@@ -122,13 +119,12 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -122,13 +119,12 @@ namespace Titanium.Web.Proxy.Helpers
await WriteAsync(data, offset, count); await WriteAsync(data, offset, count);
if (flush) if (flush)
{ {
// flush the stream
await FlushAsync(); await FlushAsync();
} }
} }
/// <summary> /// <summary>
/// Writes the byte array body to the stream; optionally chunked /// Writes the byte array body to the stream; optionally chunked
/// </summary> /// </summary>
/// <param name="data"></param> /// <param name="data"></param>
/// <param name="isChunked"></param> /// <param name="isChunked"></param>
...@@ -144,24 +140,23 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -144,24 +140,23 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream /// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// optionally chunked /// optionally chunked
/// </summary> /// </summary>
/// <param name="streamReader"></param> /// <param name="streamReader"></param>
/// <param name="isChunked"></param> /// <param name="isChunked"></param>
/// <param name="contentLength"></param> /// <param name="contentLength"></param>
/// <param name="onCopy"></param> /// <param name="onCopy"></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)
{ {
//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
//send the body bytes to server in chunks
return CopyBodyChunkedAsync(streamReader, onCopy); return CopyBodyChunkedAsync(streamReader, onCopy);
} }
//http 1.0 or the stream reader limits the stream //http 1.0 or the stream reader limits the stream
if (contentLength == -1) if (contentLength == -1)
{ {
...@@ -173,7 +168,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -173,7 +168,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <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>
/// <returns></returns> /// <returns></returns>
...@@ -191,7 +186,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -191,7 +186,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// Copies the streams chunked /// Copies the streams chunked
/// </summary> /// </summary>
/// <param name="reader"></param> /// <param name="reader"></param>
/// <param name="onCopy"></param> /// <param name="onCopy"></param>
...@@ -204,7 +199,6 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -204,7 +199,6 @@ namespace Titanium.Web.Proxy.Helpers
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);
} }
...@@ -230,7 +224,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -230,7 +224,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// Copies the specified bytes to the stream from the input stream /// Copies the specified bytes to the stream from the input stream
/// </summary> /// </summary>
/// <param name="reader"></param> /// <param name="reader"></param>
/// <param name="count"></param> /// <param name="count"></param>
...@@ -264,7 +258,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -264,7 +258,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// Writes the request/response headers and body. /// Writes the request/response headers and body.
/// </summary> /// </summary>
/// <param name="requestResponse"></param> /// <param name="requestResponse"></param>
/// <param name="flush"></param> /// <param name="flush"></param>
......
...@@ -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,11 +26,11 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -19,11 +26,11 @@ namespace Titanium.Web.Proxy.Helpers
OwnerPidAll, OwnerPidAll,
OwnerModuleListener, OwnerModuleListener,
OwnerModuleConnections, OwnerModuleConnections,
OwnerModuleAll, OwnerModuleAll
} }
/// <summary> /// <summary>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa366921.aspx"/> /// <see href="http://msdn2.microsoft.com/en-us/library/aa366921.aspx" />
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal struct TcpTable internal struct TcpTable
...@@ -33,7 +40,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -33,7 +40,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <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
...@@ -51,11 +58,5 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -51,11 +58,5 @@ namespace Titanium.Web.Proxy.Helpers
public byte remotePort4; public byte remotePort4;
public int owningPid; public int owningPid;
} }
/// <summary>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/>
/// </summary>
[DllImport("iphlpapi.dll", SetLastError = true)]
internal static extern uint GetExtendedTcpTable(IntPtr tcpTable, ref int size, bool sort, int ipVersion, int tableClass, int reserved);
} }
} }
...@@ -26,8 +26,8 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -26,8 +26,8 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// Adapated from below link /// Adapated from below link
/// http://stackoverflow.com/questions/11834091/how-to-check-if-localhost /// http://stackoverflow.com/questions/11834091/how-to-check-if-localhost
/// </summary> /// </summary>
/// <param name="address"></param> /// <param name="address"></param>
/// <returns></returns> /// <returns></returns>
...@@ -55,7 +55,9 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -55,7 +55,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;
} }
...@@ -140,7 +153,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -140,7 +153,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// Parse the system proxy setting values /// Parse the system proxy setting values
/// </summary> /// </summary>
/// <param name="proxyServerValues"></param> /// <param name="proxyServerValues"></param>
/// <returns></returns> /// <returns></returns>
...@@ -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,14 +176,16 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -161,14 +176,16 @@ 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;
} }
/// <summary> /// <summary>
/// Parses the system proxy setting string /// Parses the system proxy setting string
/// </summary> /// </summary>
/// <param name="value"></param> /// <param name="value"></param>
/// <returns></returns> /// <returns></returns>
...@@ -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
}; };
} }
} }
......
...@@ -4,26 +4,27 @@ using System.Runtime.InteropServices; ...@@ -4,26 +4,27 @@ using System.Runtime.InteropServices;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
/// <summary> /// <summary>
/// Run time helpers /// Run time helpers
/// </summary> /// </summary>
internal class RunTime internal class RunTime
{ {
/// <summary> /// <summary>
/// cache for mono runtime check /// cache for mono runtime check
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
private static readonly Lazy<bool> isRunningOnMono = new Lazy<bool>(() => Type.GetType("Mono.Runtime") != null); private static readonly Lazy<bool> isRunningOnMono = new Lazy<bool>(() => Type.GetType("Mono.Runtime") != null);
#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>
/// Is running on Mono? /// Is running on Mono?
/// </summary> /// </summary>
internal static bool IsRunningOnMono => isRunningOnMono.Value; internal static bool IsRunningOnMono => isRunningOnMono.Value;
......
...@@ -9,24 +9,24 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -9,24 +9,24 @@ namespace Titanium.Web.Proxy.Helpers
public enum ProxyProtocolType public enum ProxyProtocolType
{ {
/// <summary> /// <summary>
/// The none /// The none
/// </summary> /// </summary>
None = 0, None = 0,
/// <summary> /// <summary>
/// HTTP /// HTTP
/// </summary> /// </summary>
Http = 1, Http = 1,
/// <summary> /// <summary>
/// HTTPS /// HTTPS
/// </summary> /// </summary>
Https = 2, Https = 2,
/// <summary> /// <summary>
/// Both HTTP and HTTPS /// Both HTTP and HTTPS
/// </summary> /// </summary>
AllHttp = Http | Https, AllHttp = Http | Https
} }
internal class HttpSystemProxyValue internal class HttpSystemProxyValue
...@@ -57,7 +57,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -57,7 +57,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// Manage system proxy settings /// Manage system proxy settings
/// </summary> /// </summary>
internal class SystemProxyManager internal class SystemProxyManager
{ {
...@@ -94,7 +94,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -94,7 +94,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// Set the HTTP and/or HTTPS proxy server for current machine /// Set the HTTP and/or HTTPS proxy server for current machine
/// </summary> /// </summary>
/// <param name="hostname"></param> /// <param name="hostname"></param>
/// <param name="port"></param> /// <param name="port"></param>
...@@ -133,14 +133,15 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -133,14 +133,15 @@ 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();
} }
} }
/// <summary> /// <summary>
/// Remove the HTTP and/or HTTPS proxy setting from current machine /// Remove the HTTP and/or HTTPS proxy setting from current machine
/// </summary> /// </summary>
internal void RemoveProxy(ProxyProtocolType protocolType, bool saveOriginalConfig = true) internal void RemoveProxy(ProxyProtocolType protocolType, bool saveOriginalConfig = true)
{ {
...@@ -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
{ {
...@@ -176,7 +178,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -176,7 +178,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// Removes all types of proxy settings (both http and https) /// Removes all types of proxy settings (both http and https)
/// </summary> /// </summary>
internal void DisableAllProxy() internal void DisableAllProxy()
{ {
...@@ -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;
...@@ -301,7 +304,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -301,7 +304,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// Prepares the proxy server registry (create empty values if they don't exist) /// Prepares the proxy server registry (create empty values if they don't exist)
/// </summary> /// </summary>
/// <param name="reg"></param> /// <param name="reg"></param>
private static void PrepareRegistry(RegistryKey reg) private static void PrepareRegistry(RegistryKey reg)
...@@ -318,7 +321,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -318,7 +321,7 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// Refresh the settings so that the system know about a change in proxy setting /// Refresh the settings so that the system know about a change in proxy setting
/// </summary> /// </summary>
private void Refresh() private void Refresh()
{ {
......
...@@ -13,15 +13,15 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -13,15 +13,15 @@ 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 extended TCP table.
/// </summary> /// </summary>
/// <returns>Collection of <see cref="TcpRow"/>.</returns> /// <returns>Collection of <see cref="TcpRow" />.</returns>
internal static TcpTable GetExtendedTcpTable(IpVersion ipVersion) internal static TcpTable GetExtendedTcpTable(IpVersion ipVersion)
{ {
var tcpRows = new List<TcpRow>(); var tcpRows = new List<TcpRow>();
...@@ -37,15 +37,18 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -37,15 +37,18 @@ namespace Titanium.Web.Proxy.Helpers
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)); var table = (NativeMethods.TcpTable)Marshal.PtrToStructure(tcpTable,
typeof(NativeMethods.TcpTable));
var rowPtr = (IntPtr)((long)tcpTable + Marshal.SizeOf(table.length)); var rowPtr = (IntPtr)((long)tcpTable + Marshal.SizeOf(table.length));
for (int i = 0; i < table.length; ++i) for (int i = 0; i < table.length; ++i)
{ {
tcpRows.Add(new TcpRow((NativeMethods.TcpRow)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow)))); tcpRows.Add(new TcpRow(
(NativeMethods.TcpRow)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow))));
rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow))); rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow)));
} }
} }
...@@ -63,9 +66,9 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -63,9 +66,9 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary> /// <summary>
/// Gets the TCP row by local port number. /// Gets the TCP row by local port number.
/// </summary> /// </summary>
/// <returns><see cref="TcpRow"/>.</returns> /// <returns><see cref="TcpRow" />.</returns>
internal static TcpRow GetTcpRowByLocalPort(IpVersion ipVersion, int localPort) internal static TcpRow GetTcpRowByLocalPort(IpVersion ipVersion, int localPort)
{ {
var tcpTable = IntPtr.Zero; var tcpTable = IntPtr.Zero;
...@@ -79,15 +82,18 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -79,15 +82,18 @@ namespace Titanium.Web.Proxy.Helpers
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)); var table = (NativeMethods.TcpTable)Marshal.PtrToStructure(tcpTable,
typeof(NativeMethods.TcpTable));
var rowPtr = (IntPtr)((long)tcpTable + Marshal.SizeOf(table.length)); var rowPtr = (IntPtr)((long)tcpTable + Marshal.SizeOf(table.length));
for (int i = 0; i < table.length; ++i) for (int i = 0; i < table.length; ++i)
{ {
var tcpRow = (NativeMethods.TcpRow)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow)); var tcpRow =
(NativeMethods.TcpRow)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow));
if (tcpRow.GetLocalPort() == localPort) if (tcpRow.GetLocalPort() == localPort)
{ {
return new TcpRow(tcpRow); return new TcpRow(tcpRow);
...@@ -110,9 +116,10 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -110,9 +116,10 @@ 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
/// Usefull for websocket requests /// as prefix
/// Asynchronous Programming Model, which does not throw exceptions when the socket is closed /// Usefull for websocket requests
/// Asynchronous Programming Model, which does not throw exceptions when the socket is closed
/// </summary> /// </summary>
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <param name="serverStream"></param> /// <param name="serverStream"></param>
...@@ -122,18 +129,20 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -122,18 +129,20 @@ namespace Titanium.Web.Proxy.Helpers
/// <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, CancellationTokenSource cancellationTokenSource) Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive, ExceptionHandler exceptionFunc,
CancellationTokenSource cancellationTokenSource)
{ {
var taskCompletionSource = new TaskCompletionSource<bool>(); var taskCompletionSource = new TaskCompletionSource<bool>();
cancellationTokenSource.Token.Register(() => taskCompletionSource.TrySetResult(true)); cancellationTokenSource.Token.Register(() => taskCompletionSource.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, cancellationTokenSource, onDataSend, exceptionFunc); BeginRead(clientStream, serverStream, clientBuffer, cancellationTokenSource, onDataSend, exceptionFunc);
BeginRead(serverStream, clientStream, serverBuffer, cancellationTokenSource, onDataReceive, exceptionFunc); BeginRead(serverStream, clientStream, serverBuffer, cancellationTokenSource, onDataReceive,
exceptionFunc);
await taskCompletionSource.Task; await taskCompletionSource.Task;
} }
finally finally
...@@ -143,7 +152,8 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -143,7 +152,8 @@ namespace Titanium.Web.Proxy.Helpers
} }
} }
private static void BeginRead(Stream inputStream, Stream outputStream, byte[] buffer, CancellationTokenSource cancellationTokenSource, Action<byte[], int, int> onCopy, private static void BeginRead(Stream inputStream, Stream outputStream, byte[] buffer,
CancellationTokenSource cancellationTokenSource, Action<byte[], int, int> onCopy,
ExceptionHandler exceptionFunc) ExceptionHandler exceptionFunc)
{ {
if (cancellationTokenSource.IsCancellationRequested) if (cancellationTokenSource.IsCancellationRequested)
...@@ -182,7 +192,8 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -182,7 +192,8 @@ namespace Titanium.Web.Proxy.Helpers
try try
{ {
outputStream.EndWrite(ar2); outputStream.EndWrite(ar2);
BeginRead(inputStream, outputStream, buffer, cancellationTokenSource, onCopy, exceptionFunc); BeginRead(inputStream, outputStream, buffer, cancellationTokenSource, onCopy,
exceptionFunc);
} }
catch (IOException ex) catch (IOException ex)
{ {
...@@ -208,9 +219,10 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -208,9 +219,10 @@ 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
/// Usefull for websocket requests /// as prefix
/// Task-based Asynchronous Pattern /// Usefull for websocket requests
/// Task-based Asynchronous Pattern
/// </summary> /// </summary>
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <param name="serverStream"></param> /// <param name="serverStream"></param>
...@@ -220,11 +232,14 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -220,11 +232,14 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="exceptionFunc"></param> /// <param name="exceptionFunc"></param>
/// <returns></returns> /// <returns></returns>
private 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, CancellationTokenSource cancellationTokenSource) Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive, ExceptionHandler exceptionFunc,
CancellationTokenSource 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, cancellationTokenSource.Token); var sendRelay =
var receiveRelay = serverStream.CopyToAsync(clientStream, onDataReceive, bufferSize, cancellationTokenSource.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);
cancellationTokenSource.Cancel(); cancellationTokenSource.Cancel();
...@@ -233,8 +248,9 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -233,8 +248,9 @@ 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
/// Usefull for websocket requests /// as prefix
/// Usefull for websocket requests
/// </summary> /// </summary>
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <param name="serverStream"></param> /// <param name="serverStream"></param>
...@@ -244,10 +260,12 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -244,10 +260,12 @@ namespace Titanium.Web.Proxy.Helpers
/// <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, CancellationTokenSource cancellationTokenSource) Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive, ExceptionHandler exceptionFunc,
CancellationTokenSource cancellationTokenSource)
{ {
// todo: fix APM mode // todo: fix APM mode
return SendRawTap(clientStream, serverStream, bufferSize, onDataSend, onDataReceive, exceptionFunc, cancellationTokenSource); return SendRawTap(clientStream, serverStream, bufferSize, onDataSend, onDataReceive, exceptionFunc,
cancellationTokenSource);
} }
} }
} }
\ No newline at end of file
...@@ -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); Dispose(true);
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 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,7 +330,9 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp ...@@ -298,7 +330,9 @@ 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
{ {
...@@ -9,7 +9,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -9,7 +9,7 @@ namespace Titanium.Web.Proxy.Http
public ServerHelloInfo ServerHelloInfo { get; set; } public ServerHelloInfo ServerHelloInfo { get; set; }
/// <summary> /// <summary>
/// Creates a successfull CONNECT response /// Creates a successfull CONNECT response
/// </summary> /// </summary>
/// <param name="httpVersion"></param> /// <param name="httpVersion"></param>
/// <returns></returns> /// <returns></returns>
......
...@@ -16,28 +16,44 @@ namespace Titanium.Web.Proxy.Http ...@@ -16,28 +16,44 @@ namespace Titanium.Web.Proxy.Http
private readonly Dictionary<string, List<HttpHeader>> nonUniqueHeaders; private readonly Dictionary<string, List<HttpHeader>> nonUniqueHeaders;
/// <summary> /// <summary>
/// Unique Request header collection /// 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>
/// Unique Request header collection
/// </summary> /// </summary>
public ReadOnlyDictionary<string, HttpHeader> Headers { get; } public ReadOnlyDictionary<string, HttpHeader> Headers { get; }
/// <summary> /// <summary>
/// Non Unique headers /// Non Unique headers
/// </summary> /// </summary>
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>
/// True if header exists /// True if header exists
/// </summary> /// </summary>
/// <param name="name"></param> /// <param name="name"></param>
/// <returns></returns> /// <returns></returns>
...@@ -47,8 +63,8 @@ namespace Titanium.Web.Proxy.Http ...@@ -47,8 +63,8 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Returns all headers with given name if exists /// Returns all headers with given name if exists
/// Returns null if does'nt exist /// Returns null if does'nt exist
/// </summary> /// </summary>
/// <param name="name"></param> /// <param name="name"></param>
/// <returns></returns> /// <returns></returns>
...@@ -86,7 +102,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -86,7 +102,7 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Returns all headers /// Returns all headers
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public List<HttpHeader> GetAllHeaders() public List<HttpHeader> GetAllHeaders()
...@@ -100,7 +116,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -100,7 +116,7 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Add a new header with given name and value /// Add a new header with given name and value
/// </summary> /// </summary>
/// <param name="name"></param> /// <param name="name"></param>
/// <param name="value"></param> /// <param name="value"></param>
...@@ -110,7 +126,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -110,7 +126,7 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Adds the given header object to Request /// Adds the given header object to Request
/// </summary> /// </summary>
/// <param name="newHeader"></param> /// <param name="newHeader"></param>
public void AddHeader(HttpHeader newHeader) public void AddHeader(HttpHeader newHeader)
...@@ -142,7 +158,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -142,7 +158,7 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Adds the given header objects to Request /// Adds the given header objects to Request
/// </summary> /// </summary>
/// <param name="newHeaders"></param> /// <param name="newHeaders"></param>
public void AddHeaders(IEnumerable<HttpHeader> newHeaders) public void AddHeaders(IEnumerable<HttpHeader> newHeaders)
...@@ -159,7 +175,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -159,7 +175,7 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Adds the given header objects to Request /// Adds the given header objects to Request
/// </summary> /// </summary>
/// <param name="newHeaders"></param> /// <param name="newHeaders"></param>
public void AddHeaders(IEnumerable<KeyValuePair<string, string>> newHeaders) public void AddHeaders(IEnumerable<KeyValuePair<string, string>> newHeaders)
...@@ -176,7 +192,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -176,7 +192,7 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Adds the given header objects to Request /// Adds the given header objects to Request
/// </summary> /// </summary>
/// <param name="newHeaders"></param> /// <param name="newHeaders"></param>
public void AddHeaders(IEnumerable<KeyValuePair<string, HttpHeader>> newHeaders) public void AddHeaders(IEnumerable<KeyValuePair<string, HttpHeader>> newHeaders)
...@@ -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);
...@@ -198,11 +215,13 @@ namespace Titanium.Web.Proxy.Http ...@@ -198,11 +215,13 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// 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);
...@@ -217,7 +236,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -217,7 +236,7 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Removes given header object if it exist /// Removes given header object if it exist
/// </summary> /// </summary>
/// <param name="header">Returns true if header exists and was removed </param> /// <param name="header">Returns true if header exists and was removed </param>
public bool RemoveHeader(HttpHeader header) public bool RemoveHeader(HttpHeader header)
...@@ -242,7 +261,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -242,7 +261,7 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Removes all the headers. /// Removes all the headers.
/// </summary> /// </summary>
public void Clear() public void Clear()
{ {
...@@ -273,7 +292,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -273,7 +292,7 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Fix proxy specific headers /// Fix proxy specific headers
/// </summary> /// </summary>
internal void FixProxyHeaders() internal void FixProxyHeaders()
{ {
...@@ -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.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
...@@ -20,7 +18,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -20,7 +18,7 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Increase size of buffer and copy existing content to new buffer /// Increase size of buffer and copy existing content to new buffer
/// </summary> /// </summary>
/// <param name="buffer"></param> /// <param name="buffer"></param>
/// <param name="size"></param> /// <param name="size"></param>
......
...@@ -9,66 +9,66 @@ using Titanium.Web.Proxy.Network.Tcp; ...@@ -9,66 +9,66 @@ using Titanium.Web.Proxy.Network.Tcp;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
/// <summary> /// <summary>
/// Used to communicate with the server over HTTP(S) /// Used to communicate with the server over HTTP(S)
/// </summary> /// </summary>
public class HttpWebClient public class HttpWebClient
{ {
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>
internal TcpConnection ServerConnection { get; set; } internal TcpConnection ServerConnection { get; set; }
/// <summary> /// <summary>
/// Request ID. /// Request ID.
/// </summary> /// </summary>
public Guid RequestId { get; } public Guid RequestId { get; }
/// <summary> /// <summary>
/// Override UpStreamEndPoint for this request; Local NIC via request is made /// Override UpStreamEndPoint for this request; Local NIC via request is made
/// </summary> /// </summary>
public IPEndPoint UpStreamEndPoint { get; set; } public IPEndPoint UpStreamEndPoint { get; set; }
/// <summary> /// <summary>
/// Headers passed with Connect. /// Headers passed with Connect.
/// </summary> /// </summary>
public ConnectRequest ConnectRequest { get; internal set; } public ConnectRequest ConnectRequest { get; internal set; }
/// <summary> /// <summary>
/// Web Request. /// Web Request.
/// </summary> /// </summary>
public Request Request { get; } public Request Request { get; }
/// <summary> /// <summary>
/// Web Response. /// Web Response.
/// </summary> /// </summary>
public Response Response { get; internal set; } public Response Response { get; internal set; }
/// <summary> /// <summary>
/// PID of the process that is created the current session when client is running in this machine /// PID of the process that is created the current session when client is running in this machine
/// If client is remote then this will return /// If client is remote then this will return
/// </summary> /// </summary>
public Lazy<int> ProcessId { get; internal set; } public Lazy<int> ProcessId { get; internal set; }
/// <summary> /// <summary>
/// Is Https? /// Is Https?
/// </summary> /// </summary>
public bool IsHttps => Request.IsHttps; public bool IsHttps => Request.IsHttps;
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;
...@@ -76,7 +76,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -76,7 +76,7 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Prepare and send the http(s) request /// Prepare and send the http(s) request
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
internal async Task SendRequest(bool enable100ContinueBehaviour, bool isTransparent) internal async Task SendRequest(bool enable100ContinueBehaviour, bool isTransparent)
...@@ -95,12 +95,13 @@ namespace Titanium.Web.Proxy.Http ...@@ -95,12 +95,13 @@ namespace Titanium.Web.Proxy.Http
//Send Authentication to Upstream proxy if needed //Send Authentication to Upstream proxy if needed
if (!isTransparent && upstreamProxy != null if (!isTransparent && upstreamProxy != null
&& ServerConnection.IsHttps == false && ServerConnection.IsHttps == false
&& !string.IsNullOrEmpty(upstreamProxy.UserName) && !string.IsNullOrEmpty(upstreamProxy.UserName)
&& upstreamProxy.Password != null) && upstreamProxy.Password != null)
{ {
await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer); await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer);
await HttpHeader.GetProxyAuthorizationHeader(upstreamProxy.UserName, upstreamProxy.Password).WriteToStreamAsync(writer); await HttpHeader.GetProxyAuthorizationHeader(upstreamProxy.UserName, upstreamProxy.Password)
.WriteToStreamAsync(writer);
} }
//write request headers //write request headers
...@@ -120,7 +121,8 @@ namespace Titanium.Web.Proxy.Http ...@@ -120,7 +121,8 @@ namespace Titanium.Web.Proxy.Http
{ {
string httpStatus = await ServerConnection.StreamReader.ReadLineAsync(); string httpStatus = await ServerConnection.StreamReader.ReadLineAsync();
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
...@@ -140,14 +142,16 @@ namespace Titanium.Web.Proxy.Http ...@@ -140,14 +142,16 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Receive and parse the http response from server /// Receive and parse the http response from server
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
internal async Task ReceiveResponse() internal async Task ReceiveResponse()
{ {
//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();
if (httpStatus == null) if (httpStatus == null)
...@@ -157,7 +161,6 @@ namespace Titanium.Web.Proxy.Http ...@@ -157,7 +161,6 @@ 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(); httpStatus = await ServerConnection.StreamReader.ReadLineAsync();
} }
...@@ -199,7 +202,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -199,7 +202,7 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary> /// </summary>
internal void FinishSession() internal void FinishSession()
{ {
......
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
{ {
......
...@@ -9,33 +9,33 @@ using Titanium.Web.Proxy.Shared; ...@@ -9,33 +9,33 @@ using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
/// <summary> /// <summary>
/// Http(s) request object /// Http(s) request object
/// </summary> /// </summary>
[TypeConverter(typeof(ExpandableObjectConverter))] [TypeConverter(typeof(ExpandableObjectConverter))]
public class Request : RequestResponseBase public class Request : RequestResponseBase
{ {
/// <summary> /// <summary>
/// Request Method /// Request Method
/// </summary> /// </summary>
public string Method { get; set; } public string Method { get; set; }
/// <summary> /// <summary>
/// Request HTTP Uri /// Request HTTP Uri
/// </summary> /// </summary>
public Uri RequestUri { get; set; } public Uri RequestUri { get; set; }
/// <summary> /// <summary>
/// Is Https? /// Is Https?
/// </summary> /// </summary>
public bool IsHttps => RequestUri.Scheme == ProxyServer.UriSchemeHttps; public bool IsHttps => RequestUri.Scheme == ProxyServer.UriSchemeHttps;
/// <summary> /// <summary>
/// The original request Url. /// The original request Url.
/// </summary> /// </summary>
public string OriginalUrl { get; set; } public string OriginalUrl { get; set; }
/// <summary> /// <summary>
/// Has request body? /// Has request body?
/// </summary> /// </summary>
public override bool HasBody public override bool HasBody
{ {
...@@ -66,9 +66,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -66,9 +66,9 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Http hostname header value if exists /// Http hostname header value if exists
/// Note: Changing this does NOT change host in RequestUri /// Note: Changing this does NOT change host in RequestUri
/// Users can set new RequestUri separately /// Users can set new RequestUri separately
/// </summary> /// </summary>
public string Host public string Host
{ {
...@@ -77,7 +77,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -77,7 +77,7 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Does this request has a 100-continue header? /// Does this request has a 100-continue header?
/// </summary> /// </summary>
public bool ExpectContinue public bool ExpectContinue
{ {
...@@ -91,47 +91,17 @@ namespace Titanium.Web.Proxy.Http ...@@ -91,47 +91,17 @@ namespace Titanium.Web.Proxy.Http
public bool IsMultipartFormData => ContentType?.StartsWith("multipart/form-data") == true; public bool IsMultipartFormData => ContentType?.StartsWith("multipart/form-data") == true;
/// <summary> /// <summary>
/// Request Url /// Request Url
/// </summary> /// </summary>
public string Url => RequestUri.OriginalString; public string Url => RequestUri.OriginalString;
/// <summary> /// <summary>
/// Terminates the underlying Tcp Connection to client after current request /// Terminates the underlying Tcp Connection to client after current request
/// </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>
public bool UpgradeToWebSocket public bool UpgradeToWebSocket
{ {
...@@ -149,17 +119,17 @@ namespace Titanium.Web.Proxy.Http ...@@ -149,17 +119,17 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Does server responsed positively for 100 continue request /// Does server responsed positively for 100 continue request
/// </summary> /// </summary>
public bool Is100Continue { get; internal set; } public bool Is100Continue { get; internal set; }
/// <summary> /// <summary>
/// Server responsed negatively for the request for 100 continue /// Server responsed negatively for the request for 100 continue
/// </summary> /// </summary>
public bool ExpectationFailed { get; internal set; } public bool ExpectationFailed { get; internal set; }
/// <summary> /// <summary>
/// Gets the header text. /// Gets the header text.
/// </summary> /// </summary>
public override string HeaderText public override string HeaderText
{ {
...@@ -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;
...@@ -14,32 +12,37 @@ namespace Titanium.Web.Proxy.Http ...@@ -14,32 +12,37 @@ namespace Titanium.Web.Proxy.Http
public abstract class RequestResponseBase public abstract class RequestResponseBase
{ {
/// <summary> /// <summary>
/// Cached body content as byte array /// Cached body content as byte array
/// </summary> /// </summary>
protected byte[] BodyInternal; protected byte[] BodyInternal;
/// <summary> /// <summary>
/// Cached body as string /// Cached body as string
/// </summary> /// </summary>
private string bodyString; private string bodyString;
/// <summary> /// <summary>
/// Keeps the body data after the session is finished /// Store weather the original request/response has body or not, since the user may change the parameters
/// </summary>
internal bool OriginalHasBody;
/// <summary>
/// Keeps the body data after the session is finished
/// </summary> /// </summary>
public bool KeepBody { get; set; } public bool KeepBody { get; set; }
/// <summary> /// <summary>
/// Http Version /// Http Version
/// </summary> /// </summary>
public Version HttpVersion { get; set; } = HttpHeader.VersionUnknown; public Version HttpVersion { get; set; } = HttpHeader.VersionUnknown;
/// <summary> /// <summary>
/// Collection of all headers /// Collection of all headers
/// </summary> /// </summary>
public HeaderCollection Headers { get; } = new HeaderCollection(); public HeaderCollection Headers { get; } = new HeaderCollection();
/// <summary> /// <summary>
/// Length of the body /// Length of the body
/// </summary> /// </summary>
public long ContentLength public long ContentLength
{ {
...@@ -74,17 +77,17 @@ namespace Titanium.Web.Proxy.Http ...@@ -74,17 +77,17 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Content encoding for this request/response /// Content encoding for this request/response
/// </summary> /// </summary>
public string ContentEncoding => Headers.GetHeaderValueOrNull(KnownHeaders.ContentEncoding)?.Trim(); public string ContentEncoding => Headers.GetHeaderValueOrNull(KnownHeaders.ContentEncoding)?.Trim();
/// <summary> /// <summary>
/// Encoding for this request/response /// Encoding for this request/response
/// </summary> /// </summary>
public Encoding Encoding => HttpHelper.GetEncodingFromContentType(ContentType); public Encoding Encoding => HttpHelper.GetEncodingFromContentType(ContentType);
/// <summary> /// <summary>
/// Content-type of the request/response /// Content-type of the request/response
/// </summary> /// </summary>
public string ContentType public string ContentType
{ {
...@@ -93,7 +96,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -93,7 +96,7 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Is body send as chunked bytes /// Is body send as chunked bytes
/// </summary> /// </summary>
public bool IsChunked public bool IsChunked
{ {
...@@ -119,7 +122,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -119,7 +122,7 @@ namespace Titanium.Web.Proxy.Http
public abstract string HeaderText { get; } public abstract string HeaderText { get; }
/// <summary> /// <summary>
/// Body as byte array /// Body as byte array
/// </summary> /// </summary>
[Browsable(false)] [Browsable(false)]
public byte[] Body public byte[] Body
...@@ -140,19 +143,31 @@ namespace Titanium.Web.Proxy.Http ...@@ -140,19 +143,31 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Has the request/response body? /// Has the request/response body?
/// </summary> /// </summary>
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);
/// <summary> /// <summary>
/// get the compressed body from given bytes /// get the compressed body from given bytes
/// </summary> /// </summary>
/// <param name="encodingType"></param> /// <param name="encodingType"></param>
/// <param name="body"></param> /// <param name="body"></param>
...@@ -205,30 +220,13 @@ namespace Titanium.Web.Proxy.Http ...@@ -205,30 +220,13 @@ 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;
} }
/// <summary> /// <summary>
/// Finish the session /// Finish the session
/// </summary> /// </summary>
internal void FinishSession() internal void FinishSession()
{ {
......
...@@ -8,23 +8,38 @@ using Titanium.Web.Proxy.Shared; ...@@ -8,23 +8,38 @@ using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
/// <summary> /// <summary>
/// Http(s) response object /// Http(s) response object
/// </summary> /// </summary>
[TypeConverter(typeof(ExpandableObjectConverter))] [TypeConverter(typeof(ExpandableObjectConverter))]
public class Response : RequestResponseBase public class Response : RequestResponseBase
{ {
/// <summary> /// <summary>
/// Response Status Code. /// Constructor.
/// </summary>
public Response()
{
}
/// <summary>
/// Constructor.
/// </summary>
public Response(byte[] body)
{
Body = body;
}
/// <summary>
/// Response Status Code.
/// </summary> /// </summary>
public int StatusCode { get; set; } public int StatusCode { get; set; }
/// <summary> /// <summary>
/// Response Status description. /// Response Status description.
/// </summary> /// </summary>
public string StatusDescription { get; set; } public string StatusDescription { get; set; }
/// <summary> /// <summary>
/// Has response body? /// Has response body?
/// </summary> /// </summary>
public override bool HasBody public override bool HasBody
{ {
...@@ -57,7 +72,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -57,7 +72,7 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary> /// <summary>
/// Keep the connection alive? /// Keep the connection alive?
/// </summary> /// </summary>
public bool KeepAlive public bool KeepAlive
{ {
...@@ -79,38 +94,23 @@ namespace Titanium.Web.Proxy.Http ...@@ -79,38 +94,23 @@ 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>
public bool Is100Continue { get; internal set; } public bool Is100Continue { get; internal set; }
/// <summary> /// <summary>
/// expectation failed returned by server? /// expectation failed returned by server?
/// </summary> /// </summary>
public bool ExpectationFailed { get; internal set; } public bool ExpectationFailed { get; internal set; }
/// <summary> /// <summary>
/// Gets the resposne status. /// Gets the resposne status.
/// </summary> /// </summary>
public string Status => CreateResponseLine(HttpVersion, StatusCode, StatusDescription); public string Status => CreateResponseLine(HttpVersion, StatusCode, StatusDescription);
/// <summary> /// <summary>
/// Gets the header text. /// Gets the header text.
/// </summary> /// </summary>
public override string HeaderText public override string HeaderText
{ {
...@@ -128,19 +128,19 @@ namespace Titanium.Web.Proxy.Http ...@@ -128,19 +128,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> throw new Exception("Response body is not read yet. " +
public Response(byte[] body) "Use SessionEventArgs.GetResponseBody() or SessionEventArgs.GetResponseBodyAsString() " +
{ "method to read the response body.");
Body = body; }
} }
internal static string CreateResponseLine(Version version, int statusCode, string statusDescription) internal static string CreateResponseLine(Version version, int statusCode, string statusDescription)
...@@ -148,7 +148,8 @@ namespace Titanium.Web.Proxy.Http ...@@ -148,7 +148,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)
......
...@@ -3,12 +3,12 @@ ...@@ -3,12 +3,12 @@
namespace Titanium.Web.Proxy.Http.Responses namespace Titanium.Web.Proxy.Http.Responses
{ {
/// <summary> /// <summary>
/// 200 Ok response /// 200 Ok response
/// </summary> /// </summary>
public sealed class OkResponse : Response public sealed class OkResponse : Response
{ {
/// <summary> /// <summary>
/// Constructor. /// Constructor.
/// </summary> /// </summary>
public OkResponse() public OkResponse()
{ {
...@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy.Http.Responses ...@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy.Http.Responses
} }
/// <summary> /// <summary>
/// Constructor. /// Constructor.
/// </summary> /// </summary>
public OkResponse(byte[] body) : this() public OkResponse(byte[] body) : this()
{ {
......
...@@ -3,12 +3,12 @@ ...@@ -3,12 +3,12 @@
namespace Titanium.Web.Proxy.Http.Responses namespace Titanium.Web.Proxy.Http.Responses
{ {
/// <summary> /// <summary>
/// Redirect response /// Redirect response
/// </summary> /// </summary>
public sealed class RedirectResponse : Response public sealed class RedirectResponse : Response
{ {
/// <summary> /// <summary>
/// Constructor. /// Constructor.
/// </summary> /// </summary>
public RedirectResponse() public RedirectResponse()
{ {
......
...@@ -7,44 +7,47 @@ using Titanium.Web.Proxy.Extensions; ...@@ -7,44 +7,47 @@ using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy.Models namespace Titanium.Web.Proxy.Models
{ {
/// <summary> /// <summary>
/// A proxy endpoint that the client is aware of /// A proxy endpoint that the client is aware of
/// So client application know that it is communicating with a proxy server /// So client application know that it is communicating with a proxy server
/// </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; }
/// <summary> /// <summary>
/// Generic certificate to use for SSL decryption. /// Generic certificate to use for SSL decryption.
/// </summary> /// </summary>
public X509Certificate2 GenericCertificate { get; set; } public X509Certificate2 GenericCertificate { get; set; }
/// <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;
/// <summary> /// <summary>
/// Intercept tunnel connect response /// Intercept tunnel connect response
/// Valid only for explicit endpoints /// Valid only for explicit endpoints
/// </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)
{ {
...@@ -52,7 +55,8 @@ namespace Titanium.Web.Proxy.Models ...@@ -52,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)
{ {
...@@ -61,4 +65,4 @@ namespace Titanium.Web.Proxy.Models ...@@ -61,4 +65,4 @@ namespace Titanium.Web.Proxy.Models
} }
} }
} }
} }
\ No newline at end of file
...@@ -4,27 +4,29 @@ using System.Net; ...@@ -4,27 +4,29 @@ using System.Net;
namespace Titanium.Web.Proxy.Models namespace Titanium.Web.Proxy.Models
{ {
/// <summary> /// <summary>
/// An upstream proxy this proxy uses if any /// An upstream proxy this proxy uses if any
/// </summary> /// </summary>
public class ExternalProxy public class ExternalProxy
{ {
private static readonly Lazy<NetworkCredential> defaultCredentials = 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>
public bool UseDefaultCredentials { get; set; } public bool UseDefaultCredentials { get; set; }
/// <summary> /// <summary>
/// Bypass this proxy for connections to localhost? /// Bypass this proxy for connections to localhost?
/// </summary> /// </summary>
public bool BypassLocalhost { get; set; } public bool BypassLocalhost { get; set; }
/// <summary> /// <summary>
/// Username. /// Username.
/// </summary> /// </summary>
public string UserName public string UserName
{ {
...@@ -41,7 +43,7 @@ namespace Titanium.Web.Proxy.Models ...@@ -41,7 +43,7 @@ namespace Titanium.Web.Proxy.Models
} }
/// <summary> /// <summary>
/// Password. /// Password.
/// </summary> /// </summary>
public string Password public string Password
{ {
...@@ -58,12 +60,12 @@ namespace Titanium.Web.Proxy.Models ...@@ -58,12 +60,12 @@ namespace Titanium.Web.Proxy.Models
} }
/// <summary> /// <summary>
/// Host name. /// Host name.
/// </summary> /// </summary>
public string HostName { get; set; } public string HostName { get; set; }
/// <summary> /// <summary>
/// Port. /// Port.
/// </summary> /// </summary>
public int Port { get; set; } public int Port { get; set; }
......
...@@ -7,7 +7,7 @@ using Titanium.Web.Proxy.Http; ...@@ -7,7 +7,7 @@ using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Models namespace Titanium.Web.Proxy.Models
{ {
/// <summary> /// <summary>
/// Http Header object used by proxy /// Http Header object used by proxy
/// </summary> /// </summary>
public class HttpHeader public class HttpHeader
{ {
...@@ -20,7 +20,7 @@ namespace Titanium.Web.Proxy.Models ...@@ -20,7 +20,7 @@ namespace Titanium.Web.Proxy.Models
internal static HttpHeader ProxyConnectionKeepAlive = new HttpHeader("Proxy-Connection", "keep-alive"); internal static HttpHeader ProxyConnectionKeepAlive = new HttpHeader("Proxy-Connection", "keep-alive");
/// <summary> /// <summary>
/// Constructor. /// Constructor.
/// </summary> /// </summary>
/// <param name="name"></param> /// <param name="name"></param>
/// <param name="value"></param> /// <param name="value"></param>
...@@ -37,17 +37,17 @@ namespace Titanium.Web.Proxy.Models ...@@ -37,17 +37,17 @@ namespace Titanium.Web.Proxy.Models
} }
/// <summary> /// <summary>
/// Header Name. /// Header Name.
/// </summary> /// </summary>
public string Name { get; set; } public string Name { get; set; }
/// <summary> /// <summary>
/// Header Value. /// Header Value.
/// </summary> /// </summary>
public string Value { get; set; } public string Value { get; set; }
/// <summary> /// <summary>
/// Returns header as a valid header string /// Returns header as a valid header string
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public override string ToString() public override string ToString()
......
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
{ {
/// <summary> /// <summary>
/// An abstract endpoint where the proxy listens /// An abstract endpoint where the proxy listens
/// </summary> /// </summary>
public abstract class ProxyEndPoint public abstract class ProxyEndPoint
{ {
/// <summary> /// <summary>
/// Constructor. /// Constructor.
/// </summary> /// </summary>
/// <param name="ipAddress"></param> /// <param name="ipAddress"></param>
/// <param name="port"></param> /// <param name="port"></param>
...@@ -25,27 +22,27 @@ namespace Titanium.Web.Proxy.Models ...@@ -25,27 +22,27 @@ namespace Titanium.Web.Proxy.Models
} }
/// <summary> /// <summary>
/// underlying TCP Listener object /// underlying TCP Listener object
/// </summary> /// </summary>
internal TcpListener Listener { get; set; } internal TcpListener Listener { get; set; }
/// <summary> /// <summary>
/// Ip Address we are listening. /// Ip Address we are listening.
/// </summary> /// </summary>
public IPAddress IpAddress { get; } public IPAddress IpAddress { get; }
/// <summary> /// <summary>
/// Port we are listening. /// Port we are listening.
/// </summary> /// </summary>
public int Port { get; internal set; } public int Port { get; internal set; }
/// <summary> /// <summary>
/// Enable SSL? /// Enable SSL?
/// </summary> /// </summary>
public bool DecryptSsl { get; } public bool DecryptSsl { get; }
/// <summary> /// <summary>
/// Is IPv6 enabled? /// Is IPv6 enabled?
/// </summary> /// </summary>
public bool IpV6Enabled => Equals(IpAddress, IPAddress.IPv6Any) public bool IpV6Enabled => Equals(IpAddress, IPAddress.IPv6Any)
|| Equals(IpAddress, IPAddress.IPv6Loopback) || Equals(IpAddress, IPAddress.IPv6Loopback)
......
...@@ -6,34 +6,36 @@ using Titanium.Web.Proxy.Extensions; ...@@ -6,34 +6,36 @@ using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy.Models namespace Titanium.Web.Proxy.Models
{ {
/// <summary> /// <summary>
/// A proxy end point client is not aware of /// A proxy end point client is not aware of
/// Usefull when requests are redirected to this proxy end point through port forwarding /// Usefull when requests are redirected to this proxy end point through port forwarding
/// </summary> /// </summary>
public class TransparentProxyEndPoint : ProxyEndPoint public class TransparentProxyEndPoint : ProxyEndPoint
{ {
/// <summary> /// <summary>
/// Name of the Certificate need to be sent (same as the hostname we want to proxy) /// Constructor.
/// This is valid only when UseServerNameIndication is set to false
/// </summary>
public string GenericCertificateName { get; set; }
/// <summary>
/// Before Ssl authentication
/// </summary>
public event AsyncEventHandler<BeforeSslAuthenticateEventArgs> BeforeSslAuthenticate;
/// <summary>
/// Constructor.
/// </summary> /// </summary>
/// <param name="ipAddress"></param> /// <param name="ipAddress"></param>
/// <param name="port"></param> /// <param name="port"></param>
/// <param name="decryptSsl"></param> /// <param name="decryptSsl"></param>
public TransparentProxyEndPoint(IPAddress ipAddress, int port, bool decryptSsl = true) : base(ipAddress, port, decryptSsl) public TransparentProxyEndPoint(IPAddress ipAddress, int port, bool decryptSsl = true) : base(ipAddress, port,
decryptSsl)
{ {
GenericCertificateName = "localhost"; GenericCertificateName = "localhost";
} }
internal async Task InvokeBeforeSslAuthenticate(ProxyServer proxyServer, BeforeSslAuthenticateEventArgs connectArgs, ExceptionHandler exceptionFunc) /// <summary>
/// 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
/// </summary>
public string GenericCertificateName { get; set; }
/// <summary>
/// Before Ssl authentication
/// </summary>
public event AsyncEventHandler<BeforeSslAuthenticateEventArgs> BeforeSslAuthenticate;
internal async Task InvokeBeforeSslAuthenticate(ProxyServer proxyServer,
BeforeSslAuthenticateEventArgs connectArgs, ExceptionHandler exceptionFunc)
{ {
if (BeforeSslAuthenticate != null) if (BeforeSslAuthenticate != null)
{ {
...@@ -41,4 +43,4 @@ namespace Titanium.Web.Proxy.Models ...@@ -41,4 +43,4 @@ namespace Titanium.Web.Proxy.Models
} }
} }
} }
} }
\ No newline at end of file
...@@ -4,21 +4,21 @@ using System.Security.Cryptography.X509Certificates; ...@@ -4,21 +4,21 @@ using System.Security.Cryptography.X509Certificates;
namespace Titanium.Web.Proxy.Network namespace Titanium.Web.Proxy.Network
{ {
/// <summary> /// <summary>
/// An object that holds the cached certificate /// An object that holds the cached certificate
/// </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>
/// last time this certificate was used /// last time this certificate was used
/// 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;
}
} }
} }
...@@ -17,11 +17,12 @@ using Org.BouncyCastle.Security; ...@@ -17,11 +17,12 @@ 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
{ {
/// <summary> /// <summary>
/// Implements certificate generation operations. /// Implements certificate generation operations.
/// </summary> /// </summary>
internal class BCCertificateMaker : ICertificateMaker internal class BCCertificateMaker : ICertificateMaker
{ {
...@@ -40,7 +41,7 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -40,7 +41,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
} }
/// <summary> /// <summary>
/// Makes the certificate. /// Makes the certificate.
/// </summary> /// </summary>
/// <param name="sSubjectCn">The s subject cn.</param> /// <param name="sSubjectCn">The s subject cn.</param>
/// <param name="isRoot">if set to <c>true</c> [is root].</param> /// <param name="isRoot">if set to <c>true</c> [is root].</param>
...@@ -52,7 +53,7 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -52,7 +53,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
} }
/// <summary> /// <summary>
/// Generates the certificate. /// Generates the certificate.
/// </summary> /// </summary>
/// <param name="subjectName">Name of the subject.</param> /// <param name="subjectName">Name of the subject.</param>
/// <param name="issuerName">Name of the issuer.</param> /// <param name="issuerName">Name of the issuer.</param>
...@@ -79,7 +80,8 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -79,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
...@@ -94,12 +96,13 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -94,12 +96,13 @@ 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
var keyGenerationParameters = new KeyGenerationParameters(secureRandom, keyStrength); var keyGenerationParameters = new KeyGenerationParameters(secureRandom, keyStrength);
var keyPairGenerator = new RsaKeyPairGenerator(); var keyPairGenerator = new RsaKeyPairGenerator();
...@@ -109,13 +112,15 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -109,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);
...@@ -131,7 +136,8 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -131,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
...@@ -158,7 +164,7 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -158,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();
...@@ -173,9 +179,9 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -173,9 +179,9 @@ namespace Titanium.Web.Proxy.Network.Certificate
return new X509Certificate2(ms.ToArray(), password, X509KeyStorageFlags.Exportable); return new X509Certificate2(ms.ToArray(), password, X509KeyStorageFlags.Exportable);
} }
} }
/// <summary> /// <summary>
/// Makes the certificate internal. /// Makes the certificate internal.
/// </summary> /// </summary>
/// <param name="isRoot">if set to <c>true</c> [is root].</param> /// <param name="isRoot">if set to <c>true</c> [is root].</param>
/// <param name="hostName">hostname for certificate</param> /// <param name="hostName">hostname for certificate</param>
...@@ -184,29 +190,33 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -184,29 +190,33 @@ 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,
return GenerateCertificate(hostName, subjectName, signingCertificate.Subject, validFrom, validTo, issuerPrivateKey: kp.Private); issuerPrivateKey: kp.Private);
}
} }
/// <summary> /// <summary>
/// Makes the certificate internal. /// Makes the certificate internal.
/// </summary> /// </summary>
/// <param name="subject">The s subject cn.</param> /// <param name="subject">The s subject cn.</param>
/// <param name="isRoot">if set to <c>true</c> [is root].</param> /// <param name="isRoot">if set to <c>true</c> [is root].</param>
...@@ -216,7 +226,7 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -216,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)
...@@ -248,7 +258,9 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -248,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);
} }
} }
} }
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
namespace Titanium.Web.Proxy.Network.Certificate namespace Titanium.Web.Proxy.Network.Certificate
{ {
/// <summary> /// <summary>
/// Abstract interface for different Certificate Maker Engines /// Abstract interface for different Certificate Maker Engines
/// </summary> /// </summary>
internal interface ICertificateMaker internal interface ICertificateMaker
{ {
......
...@@ -6,47 +6,46 @@ using System.Threading; ...@@ -6,47 +6,46 @@ using System.Threading;
namespace Titanium.Web.Proxy.Network.Certificate namespace Titanium.Web.Proxy.Network.Certificate
{ {
/// <summary> /// <summary>
/// Certificate Maker - uses MakeCert /// Certificate Maker - uses MakeCert
/// Calls COM objects using reflection /// Calls COM objects using reflection
/// </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>
internal WinCertificateMaker(ExceptionHandler exceptionFunc) internal WinCertificateMaker(ExceptionHandler exceptionFunc)
{ {
...@@ -71,7 +70,7 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -71,7 +70,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
/// <summary> /// <summary>
/// Make certificate. /// Make certificate.
/// </summary> /// </summary>
/// <param name="sSubjectCN"></param> /// <param name="sSubjectCN"></param>
/// <param name="isRoot"></param> /// <param name="isRoot"></param>
...@@ -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;
} }
} }
......
...@@ -14,79 +14,129 @@ using Titanium.Web.Proxy.Shared; ...@@ -14,79 +14,129 @@ using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Network namespace Titanium.Web.Proxy.Network
{ {
/// <summary> /// <summary>
/// Certificate Engine option /// Certificate Engine option
/// </summary> /// </summary>
public enum CertificateEngine public enum CertificateEngine
{ {
/// <summary> /// <summary>
/// Uses Windows Certification Generation API /// Uses Windows Certification Generation API
/// </summary> /// </summary>
DefaultWindows = 0, DefaultWindows = 0,
/// <summary> /// <summary>
/// Uses BouncyCastle 3rd party library /// Uses BouncyCastle 3rd party library
/// </summary> /// </summary>
BouncyCastle = 1 BouncyCastle = 1
} }
/// <summary> /// <summary>
/// A class to manage SSL certificates used by this proxy server /// A class to manage SSL certificates used by this proxy server
/// </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?
/// </summary> /// </summary>
internal bool CertValidated => RootCertificate != null; internal bool CertValidated => RootCertificate != null;
/// <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
/// Optionally set to BouncyCastle /// Optionally set to BouncyCastle
/// Mono only support BouncyCastle and it is the default /// Mono only support BouncyCastle and it is the default
/// </summary> /// </summary>
public CertificateEngine CertificateEngine public CertificateEngine CertificateEngine
{ {
...@@ -115,48 +165,45 @@ namespace Titanium.Web.Proxy.Network ...@@ -115,48 +165,45 @@ namespace Titanium.Web.Proxy.Network
} }
/// <summary> /// <summary>
/// Password of the Root certificate file /// Password of the Root certificate file
/// <para>Set a password for the .pfx file</para> /// <para>Set a password for the .pfx file</para>
/// </summary> /// </summary>
public string PfxPassword { get; set; } = string.Empty; public string PfxPassword { get; set; } = string.Empty;
/// <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;
/// <summary> /// <summary>
/// Name of the root certificate issuer /// Name of the root certificate issuer
/// (This is valid only when RootCertificate property is not set) /// (This is valid only when RootCertificate property is not set)
/// </summary> /// </summary>
public string RootCertificateIssuerName public string RootCertificateIssuerName
{ {
get => issuer ?? defaultRootCertificateIssuer; get => issuer ?? defaultRootCertificateIssuer;
set set => issuer = value;
{
issuer = value;
}
} }
/// <summary> /// <summary>
/// Name of the root certificate /// Name of the root certificate
/// (This is valid only when RootCertificate property is not set) /// (This is valid only when RootCertificate property is not set)
/// If no certificate is provided then a default Root Certificate will be created and used /// If no certificate is provided then a default Root Certificate will be created and used
/// The provided root certificate will be stored in proxy exe directory with the private key /// The provided root certificate will be stored in proxy exe directory with the private key
/// Root certificate file will be named as "rootCert.pfx" /// Root certificate file will be named as "rootCert.pfx"
/// </summary> /// </summary>
public string RootCertificateName public string RootCertificateName
{ {
get => rootCertificateName ?? defaultRootRootCertificateName; get => rootCertificateName ?? defaultRootRootCertificateName;
set set => rootCertificateName = value;
{
rootCertificateName = value;
}
} }
/// <summary> /// <summary>
/// The root certificate /// The root certificate
/// </summary> /// </summary>
public X509Certificate2 RootCertificate public X509Certificate2 RootCertificate
{ {
...@@ -169,72 +216,32 @@ namespace Titanium.Web.Proxy.Network ...@@ -169,72 +216,32 @@ namespace Titanium.Web.Proxy.Network
} }
/// <summary> /// <summary>
/// Save all fake certificates in folder "crts"(will be created in proxy dll directory) /// Save all fake certificates in folder "crts"(will be created in proxy dll directory)
/// <para>for can load the certificate and not make new certificate every time </para> /// <para>for can load the certificate and not make new certificate every time </para>
/// </summary> /// </summary>
public bool SaveFakeCertificates { get; set; } = false; public bool SaveFakeCertificates { get; set; } = false;
/// <summary> /// <summary>
/// Overwrite Root certificate file /// Overwrite Root certificate file
/// <para>true : replace an existing .pfx file if password is incorect or if RootCertificate = null</para> /// <para>true : replace an existing .pfx file if password is incorect or if RootCertificate = null</para>
/// </summary> /// </summary>
public bool OverwritePfxFile { get; set; } = true; public bool OverwritePfxFile { get; set; } = true;
/// <summary> /// <summary>
/// Minutes certificates should be kept in cache when not used /// Minutes certificates should be kept in cache when not used
/// </summary> /// </summary>
public int CertificateCacheTimeOutMinutes { get; set; } = 60; public int CertificateCacheTimeOutMinutes { get; set; } = 60;
/// <summary> /// <summary>
/// Adjust behaviour when certificates are saved to filesystem /// Adjust behaviour when certificates are saved to filesystem
/// </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;
} }
...@@ -283,7 +292,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -283,7 +292,7 @@ namespace Titanium.Web.Proxy.Network
} }
/// <summary> /// <summary>
/// For CertificateEngine.DefaultWindows to work we need to also check in personal store /// For CertificateEngine.DefaultWindows to work we need to also check in personal store
/// </summary> /// </summary>
/// <param name="storeLocation"></param> /// <param name="storeLocation"></param>
/// <returns></returns> /// <returns></returns>
...@@ -291,11 +300,12 @@ namespace Titanium.Web.Proxy.Network ...@@ -291,11 +300,12 @@ namespace Titanium.Web.Proxy.Network
{ {
string value = $"{RootCertificate.Issuer}"; string value = $"{RootCertificate.Issuer}";
return FindCertificates(StoreName.Root, storeLocation, value).Count > 0 return FindCertificates(StoreName.Root, storeLocation, value).Count > 0
&& (CertificateEngine != CertificateEngine.DefaultWindows && (CertificateEngine != CertificateEngine.DefaultWindows
|| 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
...@@ -310,7 +320,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -310,7 +320,7 @@ namespace Titanium.Web.Proxy.Network
} }
/// <summary> /// <summary>
/// Make current machine trust the Root Certificate used by this proxy /// Make current machine trust the Root Certificate used by this proxy
/// </summary> /// </summary>
/// <param name="storeName"></param> /// <param name="storeName"></param>
/// <param name="storeLocation"></param> /// <param name="storeLocation"></param>
...@@ -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
{ {
...@@ -349,13 +359,14 @@ namespace Titanium.Web.Proxy.Network ...@@ -349,13 +359,14 @@ namespace Titanium.Web.Proxy.Network
} }
/// <summary> /// <summary>
/// Remove the Root Certificate trust /// Remove the Root Certificate trust
/// </summary> /// </summary>
/// <param name="storeName"></param> /// <param name="storeName"></param>
/// <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));
} }
...@@ -405,14 +415,13 @@ namespace Titanium.Web.Proxy.Network ...@@ -405,14 +415,13 @@ namespace Titanium.Web.Proxy.Network
} }
/// <summary> /// <summary>
/// Create an SSL certificate /// Create an SSL certificate
/// </summary> /// </summary>
/// <param name="certificateName"></param> /// <param name="certificateName"></param>
/// <param name="isRootCertificate"></param> /// <param name="isRootCertificate"></param>
/// <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
...@@ -464,7 +476,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -464,7 +476,7 @@ namespace Titanium.Web.Proxy.Network
} }
/// <summary> /// <summary>
/// Create an SSL certificate async /// Create an SSL certificate async
/// </summary> /// </summary>
/// <param name="certificateName"></param> /// <param name="certificateName"></param>
/// <returns></returns> /// <returns></returns>
...@@ -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,11 +519,10 @@ namespace Titanium.Web.Proxy.Network ...@@ -511,11 +519,10 @@ namespace Titanium.Web.Proxy.Network
pendingCertificateCreationTasks.TryRemove(certificateName, out task); pendingCertificateCreationTasks.TryRemove(certificateName, out task);
return certificate; return certificate;
} }
/// <summary> /// <summary>
/// A method to clear outdated certificates /// A method to clear outdated certificates
/// </summary> /// </summary>
internal async void ClearIdleCertificates() internal async void ClearIdleCertificates()
{ {
...@@ -528,7 +535,9 @@ namespace Titanium.Web.Proxy.Network ...@@ -528,7 +535,9 @@ namespace Titanium.Web.Proxy.Network
CachedCertificate removed; CachedCertificate removed;
foreach (var cache in outdated) foreach (var cache in outdated)
{
certificateCache.TryRemove(cache.Key, out removed); certificateCache.TryRemove(cache.Key, out removed);
}
//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);
...@@ -537,7 +546,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -537,7 +546,7 @@ namespace Titanium.Web.Proxy.Network
/// <summary> /// <summary>
/// Stops the certificate cache clear process /// Stops the certificate cache clear process
/// </summary> /// </summary>
internal void StopClearIdleCertificates() internal void StopClearIdleCertificates()
{ {
...@@ -545,11 +554,11 @@ namespace Titanium.Web.Proxy.Network ...@@ -545,11 +554,11 @@ namespace Titanium.Web.Proxy.Network
} }
/// <summary> /// <summary>
/// Attempts to create a RootCertificate /// Attempts to create a RootCertificate
/// </summary> /// </summary>
/// <param name="persistToFile">if set to <c>true</c> try to load/save the certificate from rootCert.pfx.</param> /// <param name="persistToFile">if set to <c>true</c> try to load/save the certificate from rootCert.pfx.</param>
/// <returns> /// <returns>
/// true if succeeded, else false /// true if succeeded, else false
/// </returns> /// </returns>
public bool CreateRootCertificate(bool persistToFile = true) public bool CreateRootCertificate(bool persistToFile = true)
{ {
...@@ -603,7 +612,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -603,7 +612,7 @@ namespace Titanium.Web.Proxy.Network
} }
/// <summary> /// <summary>
/// Loads root certificate from current executing assembly location with expected name rootCert.pfx /// Loads root certificate from current executing assembly location with expected name rootCert.pfx
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public X509Certificate2 LoadRootCertificate() public X509Certificate2 LoadRootCertificate()
...@@ -627,16 +636,20 @@ namespace Titanium.Web.Proxy.Network ...@@ -627,16 +636,20 @@ 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,16 +658,15 @@ namespace Titanium.Web.Proxy.Network ...@@ -645,16 +658,15 @@ namespace Titanium.Web.Proxy.Network
RootCertificate = LoadRootCertificate(); RootCertificate = LoadRootCertificate();
return (RootCertificate != null); return RootCertificate != null;
} }
/// <summary> /// <summary>
/// Trusts the root certificate in user store, optionally also in machine store /// Trusts the root certificate in user store, optionally also in machine store
/// Machine trust would require elevated permissions (will silently fail otherwise) /// Machine trust would require elevated permissions (will silently fail otherwise)
/// </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);
...@@ -674,8 +686,8 @@ namespace Titanium.Web.Proxy.Network ...@@ -674,8 +686,8 @@ namespace Titanium.Web.Proxy.Network
} }
/// <summary> /// <summary>
/// Puts the certificate to the user store, optionally also to machine store /// Puts the certificate to the user store, optionally also to machine store
/// Prompts with UAC if elevated permissions are required. Works only on Windows. /// Prompts with UAC if elevated permissions are required. Works only on Windows.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public bool TrustRootCertificateAsAdmin(bool machineTrusted = false) public bool TrustRootCertificateAsAdmin(bool machineTrusted = false)
...@@ -692,7 +704,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -692,7 +704,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 +733,6 @@ namespace Titanium.Web.Proxy.Network ...@@ -721,7 +733,6 @@ namespace Titanium.Web.Proxy.Network
process.WaitForExit(); process.WaitForExit();
File.Delete(pfxFileName); File.Delete(pfxFileName);
} }
catch (Exception e) catch (Exception e)
{ {
...@@ -733,8 +744,8 @@ namespace Titanium.Web.Proxy.Network ...@@ -733,8 +744,8 @@ namespace Titanium.Web.Proxy.Network
} }
/// <summary> /// <summary>
/// Ensure certificates are setup (creates root if required) /// Ensure certificates are setup (creates root if required)
/// Also makes root certificate trusted based on initial setup from proxy constructor for user/machine trust. /// Also makes root certificate trusted based on initial setup from proxy constructor for user/machine trust.
/// </summary> /// </summary>
public void EnsureRootCertificate() public void EnsureRootCertificate()
{ {
...@@ -751,15 +762,15 @@ namespace Titanium.Web.Proxy.Network ...@@ -751,15 +762,15 @@ namespace Titanium.Web.Proxy.Network
{ {
TrustRootCertificate(MachineTrustRoot); TrustRootCertificate(MachineTrustRoot);
} }
} }
/// <summary> /// <summary>
/// Ensure certificates are setup (creates root if required) /// Ensure certificates are setup (creates root if required)
/// 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)
...@@ -775,7 +786,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -775,7 +786,7 @@ namespace Titanium.Web.Proxy.Network
/// <summary> /// <summary>
/// Determines whether the root certificate is trusted. /// Determines whether the root certificate is trusted.
/// </summary> /// </summary>
public bool IsRootCertificateUserTrusted() public bool IsRootCertificateUserTrusted()
{ {
...@@ -783,7 +794,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -783,7 +794,7 @@ namespace Titanium.Web.Proxy.Network
} }
/// <summary> /// <summary>
/// Determines whether the root certificate is machine trusted. /// Determines whether the root certificate is machine trusted.
/// </summary> /// </summary>
public bool IsRootCertificateMachineTrusted() public bool IsRootCertificateMachineTrusted()
{ {
...@@ -791,8 +802,8 @@ namespace Titanium.Web.Proxy.Network ...@@ -791,8 +802,8 @@ namespace Titanium.Web.Proxy.Network
} }
/// <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
/// To remove from machine store elevated permissions are required (will fail silently otherwise) /// To remove from machine store elevated permissions are required (will fail silently otherwise)
/// </summary> /// </summary>
public void RemoveTrustedRootCertificate(bool machineTrusted = false) public void RemoveTrustedRootCertificate(bool machineTrusted = false)
{ {
...@@ -812,12 +823,10 @@ namespace Titanium.Web.Proxy.Network ...@@ -812,12 +823,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 +835,14 @@ namespace Titanium.Web.Proxy.Network ...@@ -826,14 +835,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,36 +852,38 @@ namespace Titanium.Web.Proxy.Network ...@@ -843,36 +852,38 @@ 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 {
new ProcessStartInfo(){ //currentMachine\Personal
FileName = "certutil.exe", new ProcessStartInfo
Arguments = "-delstore My \"" + RootCertificateName + "\"", {
CreateNoWindow = true, FileName = "certutil.exe",
UseShellExecute = true, Arguments = "-delstore My \"" + RootCertificateName + "\"",
Verb = "runas", CreateNoWindow = true,
ErrorDialog = false, UseShellExecute = true,
WindowStyle = ProcessWindowStyle.Hidden Verb = "runas",
}, ErrorDialog = false,
//currentUser\Personal & currentMachine\Personal WindowStyle = ProcessWindowStyle.Hidden
new ProcessStartInfo(){ },
FileName = "certutil.exe", //currentUser\Personal & currentMachine\Personal
Arguments = "-delstore Root \"" + RootCertificateName + "\"", new ProcessStartInfo
CreateNoWindow = true, {
UseShellExecute = true, FileName = "certutil.exe",
Verb = "runas", Arguments = "-delstore Root \"" + RootCertificateName + "\"",
ErrorDialog = false, CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden UseShellExecute = true,
} Verb = "runas",
}); ErrorDialog = false,
WindowStyle = ProcessWindowStyle.Hidden
}
});
} }
var success = true; bool success = true;
try try
{ {
foreach (var info in infos) foreach (var info in infos)
...@@ -900,12 +911,5 @@ namespace Titanium.Web.Proxy.Network ...@@ -900,12 +911,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);
...@@ -47,4 +42,4 @@ namespace Titanium.Web.Proxy.Network ...@@ -47,4 +42,4 @@ namespace Titanium.Web.Proxy.Network
} }
} }
} }
#endif #endif
\ No newline at end of file
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
{ {
/// <summary> /// <summary>
/// This class wraps Tcp connection to client /// This class wraps Tcp connection to client
/// </summary> /// </summary>
internal class ProxyClient internal class ProxyClient
{ {
/// <summary> /// <summary>
/// TcpClient used to communicate with client /// TcpClient used to communicate with client
/// </summary> /// </summary>
internal TcpClient TcpClient { get; set; } internal TcpClient TcpClient { get; set; }
/// <summary> /// <summary>
/// Holds the stream to client /// Holds the stream to client
/// </summary> /// </summary>
internal CustomBufferedStream ClientStream { get; set; } internal CustomBufferedStream ClientStream { get; set; }
/// <summary> /// <summary>
/// Used to read line by line from client /// Used to read line by line from client
/// </summary> /// </summary>
internal CustomBinaryReader ClientStreamReader { get; set; } internal CustomBinaryReader ClientStreamReader { get; set; }
/// <summary> /// <summary>
/// Used to write line by line to client /// Used to write line by line to client
/// </summary> /// </summary>
internal HttpResponseWriter ClientStreamWriter { get; set; } internal HttpResponseWriter ClientStreamWriter { get; set; }
} }
......
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;
...@@ -9,10 +9,17 @@ using Titanium.Web.Proxy.Models; ...@@ -9,10 +9,17 @@ using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network.Tcp namespace Titanium.Web.Proxy.Network.Tcp
{ {
/// <summary> /// <summary>
/// An object that holds TcpConnection to a particular server and port /// An object that holds TcpConnection to a particular server and port
/// </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; }
...@@ -26,46 +33,39 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -26,46 +33,39 @@ namespace Titanium.Web.Proxy.Network.Tcp
internal bool UseUpstreamProxy { get; set; } internal bool UseUpstreamProxy { get; set; }
/// <summary> /// <summary>
/// Local NIC via connection is made /// Local NIC via connection is made
/// </summary> /// </summary>
internal IPEndPoint UpStreamEndPoint { get; set; } internal IPEndPoint UpStreamEndPoint { get; set; }
/// <summary> /// <summary>
/// Http version /// Http version
/// </summary> /// </summary>
internal Version Version { get; set; } internal Version Version { get; set; }
internal TcpClient TcpClient { private get; set; } internal TcpClient TcpClient { private get; set; }
/// <summary> /// <summary>
/// Used to read lines from server /// Used to read lines from server
/// </summary> /// </summary>
internal CustomBinaryReader StreamReader { get; set; } internal CustomBinaryReader StreamReader { get; set; }
/// <summary> /// <summary>
/// Used to write lines to server /// Used to write lines to server
/// </summary> /// </summary>
internal HttpRequestWriter StreamWriter { get; set; } internal HttpRequestWriter StreamWriter { get; set; }
/// <summary> /// <summary>
/// Server stream /// Server stream
/// </summary> /// </summary>
internal CustomBufferedStream Stream { get; set; } internal CustomBufferedStream Stream { get; set; }
/// <summary> /// <summary>
/// Last time this connection was used /// Last time this connection was used
/// </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>
public void Dispose() public void Dispose()
{ {
......
using StreamExtended.Network; using System;
using System;
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.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;
...@@ -14,12 +13,12 @@ using Titanium.Web.Proxy.Models; ...@@ -14,12 +13,12 @@ using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network.Tcp namespace Titanium.Web.Proxy.Network.Tcp
{ {
/// <summary> /// <summary>
/// A class that manages Tcp Connection to server used by this proxy server /// A class that manages Tcp Connection to server used by this proxy server
/// </summary> /// </summary>
internal class TcpConnectionFactory internal class TcpConnectionFactory
{ {
/// <summary> /// <summary>
/// Creates a TCP connection to server /// Creates a TCP connection to server
/// </summary> /// </summary>
/// <param name="remoteHostName"></param> /// <param name="remoteHostName"></param>
/// <param name="remotePort"></param> /// <param name="remotePort"></param>
...@@ -37,7 +36,8 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -37,7 +36,8 @@ namespace Titanium.Web.Proxy.Network.Tcp
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;
...@@ -90,8 +90,8 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -90,8 +90,8 @@ namespace Titanium.Web.Proxy.Network.Tcp
Response.ParseResponseLine(httpStatus, out _, out int statusCode, out string statusDescription); Response.ParseResponseLine(httpStatus, out _, out int statusCode, out string statusDescription);
if (statusCode != 200 && !statusDescription.EqualsIgnoreCase("OK") if (statusCode != 200 && !statusDescription.EqualsIgnoreCase("OK")
&& !statusDescription.EqualsIgnoreCase("Connection Established")) && !statusDescription.EqualsIgnoreCase("Connection Established"))
{ {
throw new Exception("Upstream proxy failed to create a secure tunnel"); throw new Exception("Upstream proxy failed to create a secure tunnel");
} }
...@@ -102,10 +102,12 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -102,10 +102,12 @@ namespace Titanium.Web.Proxy.Network.Tcp
if (isHttps) if (isHttps)
{ {
var sslStream = new SslStream(stream, false, proxyServer.ValidateServerCertificate, proxyServer.SelectClientCertificate); var sslStream = new SslStream(stream, false, proxyServer.ValidateServerCertificate,
proxyServer.SelectClientCertificate);
stream = new CustomBufferedStream(sslStream, proxyServer.BufferSize); stream = new CustomBufferedStream(sslStream, proxyServer.BufferSize);
await sslStream.AuthenticateAsClientAsync(remoteHostName, null, proxyServer.SupportedSslProtocols, proxyServer.CheckCertificateRevocation); await sslStream.AuthenticateAsClientAsync(remoteHostName, null, proxyServer.SupportedSslProtocols,
proxyServer.CheckCertificateRevocation);
} }
client.ReceiveTimeout = proxyServer.ConnectionTimeOutSeconds * 1000; client.ReceiveTimeout = proxyServer.ConnectionTimeOutSeconds * 1000;
......
...@@ -5,13 +5,13 @@ using Titanium.Web.Proxy.Helpers; ...@@ -5,13 +5,13 @@ using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Network.Tcp namespace Titanium.Web.Proxy.Network.Tcp
{ {
/// <summary> /// <summary>
/// Represents a managed interface of IP Helper API TcpRow struct /// Represents a managed interface of IP Helper API TcpRow struct
/// <see href="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/> /// <see href="http://msdn2.microsoft.com/en-us/library/aa366913.aspx" />
/// </summary> /// </summary>
internal class TcpRow internal class TcpRow
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TcpRow"/> class. /// Initializes a new instance of the <see cref="TcpRow" /> class.
/// </summary> /// </summary>
/// <param name="tcpRow">TcpRow struct.</param> /// <param name="tcpRow">TcpRow struct.</param>
internal TcpRow(NativeMethods.TcpRow tcpRow) internal TcpRow(NativeMethods.TcpRow tcpRow)
...@@ -26,37 +26,37 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -26,37 +26,37 @@ namespace Titanium.Web.Proxy.Network.Tcp
} }
/// <summary> /// <summary>
/// Gets the local end point address. /// Gets the local end point address.
/// </summary> /// </summary>
internal long LocalAddress { get; } internal long LocalAddress { get; }
/// <summary> /// <summary>
/// Gets the local end point port. /// Gets the local end point port.
/// </summary> /// </summary>
internal int LocalPort { get; } internal int LocalPort { get; }
/// <summary> /// <summary>
/// Gets the local end point. /// Gets the local end point.
/// </summary> /// </summary>
internal IPEndPoint LocalEndPoint => new IPEndPoint(LocalAddress, LocalPort); internal IPEndPoint LocalEndPoint => new IPEndPoint(LocalAddress, LocalPort);
/// <summary> /// <summary>
/// Gets the remote end point address. /// Gets the remote end point address.
/// </summary> /// </summary>
internal long RemoteAddress { get; } internal long RemoteAddress { get; }
/// <summary> /// <summary>
/// Gets the remote end point port. /// Gets the remote end point port.
/// </summary> /// </summary>
internal int RemotePort { get; } internal int RemotePort { get; }
/// <summary> /// <summary>
/// Gets the remote end point. /// Gets the remote end point.
/// </summary> /// </summary>
internal IPEndPoint RemoteEndPoint => new IPEndPoint(RemoteAddress, RemotePort); internal IPEndPoint RemoteEndPoint => new IPEndPoint(RemoteAddress, RemotePort);
/// <summary> /// <summary>
/// Gets the process identifier. /// Gets the process identifier.
/// </summary> /// </summary>
internal int ProcessId { get; } internal int ProcessId { get; }
} }
......
...@@ -4,7 +4,7 @@ using System.Collections.Generic; ...@@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Titanium.Web.Proxy.Network.Tcp namespace Titanium.Web.Proxy.Network.Tcp
{ {
/// <summary> /// <summary>
/// Represents collection of TcpRows /// Represents collection of TcpRows
/// </summary> /// </summary>
/// <seealso> /// <seealso>
/// <cref>System.Collections.Generic.IEnumerable{Proxy.Tcp.TcpRow}</cref> /// <cref>System.Collections.Generic.IEnumerable{Proxy.Tcp.TcpRow}</cref>
...@@ -12,7 +12,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -12,7 +12,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
internal class TcpTable : IEnumerable<TcpRow> internal class TcpTable : IEnumerable<TcpRow>
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TcpTable"/> class. /// Initializes a new instance of the <see cref="TcpTable" /> class.
/// </summary> /// </summary>
/// <param name="tcpRows">TcpRow collection to initialize with.</param> /// <param name="tcpRows">TcpRow collection to initialize with.</param>
internal TcpTable(IEnumerable<TcpRow> tcpRows) internal TcpTable(IEnumerable<TcpRow> tcpRows)
...@@ -21,12 +21,12 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -21,12 +21,12 @@ namespace Titanium.Web.Proxy.Network.Tcp
} }
/// <summary> /// <summary>
/// Gets the TCP rows. /// Gets the TCP rows.
/// </summary> /// </summary>
internal IEnumerable<TcpRow> TcpRows { get; } internal IEnumerable<TcpRow> TcpRows { get; }
/// <summary> /// <summary>
/// Returns an enumerator that iterates through the collection. /// Returns an enumerator that iterates through the collection.
/// </summary> /// </summary>
/// <returns>An enumerator that can be used to iterate through the collection.</returns> /// <returns>An enumerator that can be used to iterate through the collection.</returns>
public IEnumerator<TcpRow> GetEnumerator() public IEnumerator<TcpRow> GetEnumerator()
...@@ -35,7 +35,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -35,7 +35,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
} }
/// <summary> /// <summary>
/// Returns an enumerator that iterates through a collection. /// Returns an enumerator that iterates through a collection.
/// </summary> /// </summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns> /// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.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
...@@ -105,7 +107,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -105,7 +107,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
} }
/// <summary> /// <summary>
/// Resets all internal pointers to default value /// Resets all internal pointers to default value
/// </summary> /// </summary>
internal void Reset() internal void Reset()
{ {
...@@ -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,17 +149,21 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -149,17 +149,21 @@ 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)];
}
} }
} }
......
...@@ -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;
...@@ -49,17 +51,15 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -49,17 +51,15 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
} }
/// <summary> /// <summary>
/// Domain name /// Domain name
/// </summary> /// </summary>
internal string Domain { get; private set; } internal string Domain { get; private set; }
/// <summary> /// <summary>
/// Username /// Username
/// </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;
} }
} }
} }
...@@ -3,12 +3,12 @@ ...@@ -3,12 +3,12 @@
namespace Titanium.Web.Proxy.Network.WinAuth.Security namespace Titanium.Web.Proxy.Network.WinAuth.Security
{ {
/// <summary> /// <summary>
/// Status of authenticated session /// Status of authenticated session
/// </summary> /// </summary>
internal class State internal class State
{ {
/// <summary> /// <summary>
/// States during Windows Authentication /// States during Windows Authentication
/// </summary> /// </summary>
public enum WinAuthState public enum WinAuthState
{ {
...@@ -16,36 +16,36 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -16,36 +16,36 @@ 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
/// </summary> /// </summary>
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()
{ {
......
...@@ -15,13 +15,13 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -15,13 +15,13 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
internal class WinAuthEndPoint internal class WinAuthEndPoint
{ {
/// <summary> /// <summary>
/// Keep track of auth states for reuse in final challenge response /// Keep track of auth states for reuse in final challenge response
/// </summary> /// </summary>
private static readonly IDictionary<Guid, State> authStates = new ConcurrentDictionary<Guid, State>(); private static readonly IDictionary<Guid, State> authStates = new ConcurrentDictionary<Guid, State>();
/// <summary> /// <summary>
/// Acquire the intial client token to send /// Acquire the intial client token to send
/// </summary> /// </summary>
/// <param name="hostname"></param> /// <param name="hostname"></param>
/// <param name="authScheme"></param> /// <param name="authScheme"></param>
...@@ -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;
} }
...@@ -91,7 +89,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -91,7 +89,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
} }
/// <summary> /// <summary>
/// Acquire the final token to send /// Acquire the final token to send
/// </summary> /// </summary>
/// <param name="hostname"></param> /// <param name="hostname"></param>
/// <param name="serverChallenge"></param> /// <param name="serverChallenge"></param>
...@@ -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;
} }
...@@ -145,7 +142,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -145,7 +142,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
} }
/// <summary> /// <summary>
/// Clear any hanging states /// Clear any hanging states
/// </summary> /// </summary>
/// <param name="stateCacheTimeOutMinutes"></param> /// <param name="stateCacheTimeOutMinutes"></param>
internal static async void ClearIdleStates(int stateCacheTimeOutMinutes) internal static async void ClearIdleStates(int stateCacheTimeOutMinutes)
...@@ -164,37 +161,37 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -164,37 +161,37 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
} }
/// <summary> /// <summary>
/// Validates that the current WinAuth state of the connection matches the /// Validates that the current WinAuth state of the connection matches the
/// expectation, used to detect failed authentication /// expectation, used to detect failed authentication
/// </summary> /// </summary>
/// <param name="requestId"></param> /// <param name="requestId"></param>
/// <param name="expectedAuthState"></param> /// <param name="expectedAuthState"></param>
/// <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");
} }
/// <summary> /// <summary>
/// Set the AuthState to authorized and update the connection state lifetime /// Set the AuthState to authorized and update the connection state lifetime
/// </summary> /// </summary>
/// <param name="requestId"></param> /// <param name="requestId"></param>
internal static void AuthenticatedResponse(Guid requestId) internal static void AuthenticatedResponse(Guid requestId)
...@@ -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,
......
...@@ -4,15 +4,15 @@ using Titanium.Web.Proxy.Network.WinAuth.Security; ...@@ -4,15 +4,15 @@ using Titanium.Web.Proxy.Network.WinAuth.Security;
namespace Titanium.Web.Proxy.Network.WinAuth namespace Titanium.Web.Proxy.Network.WinAuth
{ {
/// <summary> /// <summary>
/// A handler for NTLM/Kerberos windows authentication challenge from server /// A handler for NTLM/Kerberos windows authentication challenge from server
/// NTLM process details below /// NTLM process details below
/// https://blogs.msdn.microsoft.com/chiranth/2013/09/20/ntlm-want-to-know-how-it-works/ /// https://blogs.msdn.microsoft.com/chiranth/2013/09/20/ntlm-want-to-know-how-it-works/
/// </summary> /// </summary>
public static class WinAuthHandler public static class WinAuthHandler
{ {
/// <summary> /// <summary>
/// Get the initial client token for server /// Get the initial client token for server
/// using credentials of user running the proxy server process /// using credentials of user running the proxy server process
/// </summary> /// </summary>
/// <param name="serverHostname"></param> /// <param name="serverHostname"></param>
/// <param name="authScheme"></param> /// <param name="authScheme"></param>
...@@ -26,7 +26,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth ...@@ -26,7 +26,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth
/// <summary> /// <summary>
/// Get the final token given the server challenge token /// Get the final token given the server challenge token
/// </summary> /// </summary>
/// <param name="serverHostname"></param> /// <param name="serverHostname"></param>
/// <param name="serverToken"></param> /// <param name="serverToken"></param>
...@@ -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));
} }
......
...@@ -32,7 +32,8 @@ namespace Titanium.Web.Proxy ...@@ -32,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");
...@@ -53,7 +54,6 @@ namespace Titanium.Web.Proxy ...@@ -53,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");
} }
...@@ -61,7 +61,8 @@ namespace Titanium.Web.Proxy ...@@ -61,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");
......
...@@ -26,99 +26,143 @@ namespace Titanium.Web.Proxy ...@@ -26,99 +26,143 @@ namespace Titanium.Web.Proxy
internal static readonly string UriSchemeHttps = Uri.UriSchemeHttps; internal static readonly string UriSchemeHttps = Uri.UriSchemeHttps;
/// <summary> /// <summary>
/// Backing field for corresponding public property /// An default exception log func
/// </summary>
private readonly Lazy<ExceptionHandler> defaultExceptionFunc = new Lazy<ExceptionHandler>(() => (e => { }));
/// <summary>
/// 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
/// </summary> /// </summary>
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> /// </summary>
private readonly Lazy<ExceptionHandler> defaultExceptionFunc = new Lazy<ExceptionHandler>(() => (e => { })); /// <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>
/// backing exception func for exposed public property /// A object that creates tcp connection to server
/// </summary> /// </summary>
private ExceptionHandler exceptionFunc; private TcpConnectionFactory tcpConnectionFactory { get; }
/// <summary>
/// Manage system proxy settings
/// </summary>
private SystemProxyManager systemProxySettingsManager { get; }
/// <summary> /// <summary>
/// Is the proxy currently running /// Is the proxy currently running
/// </summary> /// </summary>
public bool ProxyRunning { get; private set; } public bool ProxyRunning { get; private set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether requests will be chained to upstream gateway. /// Gets or sets a value indicating whether requests will be chained to upstream gateway.
/// </summary> /// </summary>
public bool ForwardToUpstreamGateway { get; set; } public bool ForwardToUpstreamGateway { get; set; }
/// <summary> /// <summary>
/// Enable disable Windows Authentication (NTLM/Kerberos) /// Enable disable Windows Authentication (NTLM/Kerberos)
/// Note: NTLM/Kerberos will always send local credentials of current user /// Note: NTLM/Kerberos will always send local credentials of current user
/// who is running the proxy process. This is because a man /// who is running the proxy process. This is because a man
/// in middle attack is not currently supported /// in middle attack is not currently supported
/// (which would require windows delegation enabled for this server process) /// (which would require windows delegation enabled for this server process)
/// </summary> /// </summary>
public bool EnableWinAuth { get; set; } public bool EnableWinAuth { get; set; }
/// <summary> /// <summary>
/// Should we check for certificare revocation during SSL authentication to servers /// Should we check for certificare revocation during SSL authentication to servers
/// Note: If enabled can reduce performance (Default disabled) /// Note: If enabled can reduce performance (Default disabled)
/// </summary> /// </summary>
public bool CheckCertificateRevocation { get; set; } public bool CheckCertificateRevocation { get; set; }
/// <summary> /// <summary>
/// Does this proxy uses the HTTP protocol 100 continue behaviour strictly? /// Does this proxy uses the HTTP protocol 100 continue behaviour strictly?
/// Broken 100 contunue implementations on server/client may cause problems if enabled /// Broken 100 contunue implementations on server/client may cause problems if enabled
/// </summary> /// </summary>
public bool Enable100ContinueBehaviour { get; set; } public bool Enable100ContinueBehaviour { get; set; }
/// <summary> /// <summary>
/// Buffer size used throughout this proxy /// Buffer size used throughout this proxy
/// </summary> /// </summary>
public int BufferSize { get; set; } = 8192; public int BufferSize { get; set; } = 8192;
/// <summary> /// <summary>
/// Seconds client/server connection are to be kept alive when waiting for read/write to complete /// Seconds client/server connection are to be kept alive when waiting for read/write to complete
/// </summary> /// </summary>
public int ConnectionTimeOutSeconds { get; set; } public int ConnectionTimeOutSeconds { get; set; }
/// <summary> /// <summary>
/// Total number of active client connections /// Total number of active client connections
/// </summary> /// </summary>
public int ClientConnectionCount => clientConnectionCount; public int ClientConnectionCount => clientConnectionCount;
/// <summary> /// <summary>
/// Total number of active server connections /// Total number of active server connections
/// </summary> /// </summary>
public int ServerConnectionCount => serverConnectionCount; public int ServerConnectionCount => serverConnectionCount;
/// <summary> /// <summary>
/// Realm used during Proxy Basic Authentication /// Realm used during Proxy Basic Authentication
/// </summary> /// </summary>
public string ProxyRealm { get; set; } = "TitaniumProxy"; public string ProxyRealm { get; set; } = "TitaniumProxy";
/// <summary> /// <summary>
/// List of supported Ssl versions /// List of supported Ssl versions
/// </summary> /// </summary>
public SslProtocols SupportedSslProtocols { get; set; } = public SslProtocols SupportedSslProtocols { get; set; } =
#if NET45 #if NET45
...@@ -128,128 +172,109 @@ namespace Titanium.Web.Proxy ...@@ -128,128 +172,109 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// Manages certificates used by this proxy /// Manages certificates used by this proxy
/// </summary> /// </summary>
public CertificateManager CertificateManager { get; } public CertificateManager CertificateManager { get; }
/// <summary> /// <summary>
/// External proxy for Http /// External proxy for Http
/// </summary> /// </summary>
public ExternalProxy UpStreamHttpProxy { get; set; } public ExternalProxy UpStreamHttpProxy { get; set; }
/// <summary> /// <summary>
/// External proxy for Http /// External proxy for Http
/// </summary> /// </summary>
public ExternalProxy UpStreamHttpsProxy { get; set; } public ExternalProxy UpStreamHttpsProxy { get; set; }
/// <summary> /// <summary>
/// Local adapter/NIC endpoint (where proxy makes request via) /// Local adapter/NIC endpoint (where proxy makes request via)
/// default via any IP addresses of this machine /// default via any IP addresses of this machine
/// </summary> /// </summary>
public IPEndPoint UpStreamEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0); public IPEndPoint UpStreamEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0);
/// <summary> /// <summary>
/// A list of IpAddress and port this proxy is listening to /// A list of IpAddress and port this proxy is listening to
/// </summary> /// </summary>
public List<ProxyEndPoint> ProxyEndPoints { get; set; } public List<ProxyEndPoint> ProxyEndPoints { get; set; }
/// <summary> /// <summary>
/// Occurs when client connection count changed. /// 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> /// </summary>
public event EventHandler ClientConnectionCountChanged; public Func<SessionEventArgsBase, Task<ExternalProxy>> GetCustomUpStreamProxyFunc { get; set; }
/// <summary> /// <summary>
/// Occurs when server connection count changed. /// Callback for error events in proxy
/// </summary> /// </summary>
public event EventHandler ServerConnectionCountChanged; public ExceptionHandler ExceptionFunc
{
get => exceptionFunc ?? defaultExceptionFunc.Value;
set => exceptionFunc = value;
}
/// <summary> /// <summary>
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication /// A callback to authenticate clients
/// Parameters are username, password provided by client
/// return true for successful authentication
/// </summary> /// </summary>
public event AsyncEventHandler<CertificateValidationEventArgs> ServerCertificateValidationCallback; public Func<string, string, Task<bool>> AuthenticateUserFunc { get; set; }
/// <summary> /// <summary>
/// Callback tooverride client certificate during SSL mutual authentication /// Dispose Proxy.
/// </summary> /// </summary>
public event AsyncEventHandler<CertificateSelectionEventArgs> ClientCertificateSelectionCallback; public void Dispose()
{
if (ProxyRunning)
{
Stop();
}
/// <summary> CertificateManager?.Dispose();
/// 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 /// Occurs when client connection count changed.
/// </summary> /// </summary>
public event AsyncEventHandler<SessionEventArgs> BeforeRequest; public event EventHandler ClientConnectionCountChanged;
/// <summary> /// <summary>
/// Intercept response from server /// Occurs when server connection count changed.
/// </summary> /// </summary>
public event AsyncEventHandler<SessionEventArgs> BeforeResponse; public event EventHandler ServerConnectionCountChanged;
/// <summary> /// <summary>
/// Intercept after response from server /// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication
/// </summary> /// </summary>
public event AsyncEventHandler<SessionEventArgs> AfterResponse; public event AsyncEventHandler<CertificateValidationEventArgs> ServerCertificateValidationCallback;
/// <summary> /// <summary>
/// Callback for error events in proxy /// Callback tooverride client certificate during SSL mutual authentication
/// </summary> /// </summary>
public ExceptionHandler ExceptionFunc public event AsyncEventHandler<CertificateSelectionEventArgs> ClientCertificateSelectionCallback;
{
get => exceptionFunc ?? defaultExceptionFunc.Value;
set => exceptionFunc = value;
}
/// <summary> /// <summary>
/// A callback to authenticate clients /// Intercept request to server
/// Parameters are username, password provided by client
/// return true for successful authentication
/// </summary> /// </summary>
public Func<string, string, Task<bool>> AuthenticateUserFunc { get; set; } public event AsyncEventHandler<SessionEventArgs> BeforeRequest;
/// <summary> /// <summary>
/// Constructor /// Intercept response from server
/// </summary> /// </summary>
/// <param name="userTrustRootCertificate"></param> public event AsyncEventHandler<SessionEventArgs> BeforeResponse;
/// <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>
/// Constructor. /// Intercept after response from server
/// </summary> /// </summary>
/// <param name="rootCertificateName">Name of root certificate.</param> public event AsyncEventHandler<SessionEventArgs> AfterResponse;
/// <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");
} }
...@@ -263,8 +288,8 @@ namespace Titanium.Web.Proxy ...@@ -263,8 +288,8 @@ namespace Titanium.Web.Proxy
} }
/// <summary> /// <summary>
/// Remove a proxy end point /// Remove a proxy end point
/// Will throw error if the end point does'nt exist /// Will throw error if the end point does'nt exist
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
public void RemoveEndPoint(ProxyEndPoint endPoint) public void RemoveEndPoint(ProxyEndPoint endPoint)
...@@ -283,7 +308,7 @@ namespace Titanium.Web.Proxy ...@@ -283,7 +308,7 @@ namespace Titanium.Web.Proxy
} }
/// <summary> /// <summary>
/// Set the given explicit end point as the default proxy server for current machine /// Set the given explicit end point as the default proxy server for current machine
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint) public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint)
...@@ -292,7 +317,7 @@ namespace Titanium.Web.Proxy ...@@ -292,7 +317,7 @@ namespace Titanium.Web.Proxy
} }
/// <summary> /// <summary>
/// Set the given explicit end point as the default proxy server for current machine /// Set the given explicit end point as the default proxy server for current machine
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint) public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint)
...@@ -301,7 +326,7 @@ namespace Titanium.Web.Proxy ...@@ -301,7 +326,7 @@ namespace Titanium.Web.Proxy
} }
/// <summary> /// <summary>
/// Set the given explicit end point as the default proxy server for current machine /// Set the given explicit end point as the default proxy server for current machine
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
/// <param name="protocolType"></param> /// <param name="protocolType"></param>
...@@ -316,10 +341,9 @@ namespace Titanium.Web.Proxy ...@@ -316,10 +341,9 @@ namespace Titanium.Web.Proxy
bool isHttp = (protocolType & ProxyProtocolType.Http) > 0; bool isHttp = (protocolType & ProxyProtocolType.Http) > 0;
bool isHttps = (protocolType & ProxyProtocolType.Https) > 0; bool isHttps = (protocolType & ProxyProtocolType.Https) > 0;
if (isHttps) if (isHttps)
{ {
CertificateManager.EnsureRootCertificate(); CertificateManager.EnsureRootCertificate();
//If certificate was trusted by the machine //If certificate was trusted by the machine
...@@ -375,12 +399,13 @@ namespace Titanium.Web.Proxy ...@@ -375,12 +399,13 @@ 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);
} }
} }
/// <summary> /// <summary>
/// Remove any HTTP proxy setting of current machien /// Remove any HTTP proxy setting of current machien
/// </summary> /// </summary>
public void DisableSystemHttpProxy() public void DisableSystemHttpProxy()
{ {
...@@ -388,7 +413,7 @@ namespace Titanium.Web.Proxy ...@@ -388,7 +413,7 @@ namespace Titanium.Web.Proxy
} }
/// <summary> /// <summary>
/// Remove any HTTPS proxy setting for current machine /// Remove any HTTPS proxy setting for current machine
/// </summary> /// </summary>
public void DisableSystemHttpsProxy() public void DisableSystemHttpsProxy()
{ {
...@@ -396,7 +421,7 @@ namespace Titanium.Web.Proxy ...@@ -396,7 +421,7 @@ namespace Titanium.Web.Proxy
} }
/// <summary> /// <summary>
/// Remove the specified proxy settings for current machine /// Remove the specified proxy settings for current machine
/// </summary> /// </summary>
public void DisableSystemProxy(ProxyProtocolType protocolType) public void DisableSystemProxy(ProxyProtocolType protocolType)
{ {
...@@ -409,7 +434,7 @@ namespace Titanium.Web.Proxy ...@@ -409,7 +434,7 @@ namespace Titanium.Web.Proxy
} }
/// <summary> /// <summary>
/// Clear all proxy settings for current machine /// Clear all proxy settings for current machine
/// </summary> /// </summary>
public void DisableAllSystemProxies() public void DisableAllSystemProxies()
{ {
...@@ -422,7 +447,7 @@ namespace Titanium.Web.Proxy ...@@ -422,7 +447,7 @@ namespace Titanium.Web.Proxy
} }
/// <summary> /// <summary>
/// Start this proxy server /// Start this proxy server
/// </summary> /// </summary>
public void Start() public void Start()
{ {
...@@ -447,7 +472,7 @@ namespace Titanium.Web.Proxy ...@@ -447,7 +472,7 @@ namespace Titanium.Web.Proxy
foreach (var proxy in proxyInfo.Proxies.Values) foreach (var proxy in proxyInfo.Proxies.Values)
{ {
if ((proxy.HostName == "127.0.0.1" if ((proxy.HostName == "127.0.0.1"
|| proxy.HostName.Equals("localhost", StringComparison.OrdinalIgnoreCase)) || proxy.HostName.Equals("localhost", StringComparison.OrdinalIgnoreCase))
&& ProxyEndPoints.Any(x => x.Port == proxy.Port)) && ProxyEndPoints.Any(x => x.Port == proxy.Port))
{ {
protocolToRemove |= proxy.ProtocolType; protocolToRemove |= proxy.ProtocolType;
...@@ -456,7 +481,6 @@ namespace Titanium.Web.Proxy ...@@ -456,7 +481,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,13 +506,12 @@ namespace Titanium.Web.Proxy ...@@ -482,13 +506,12 @@ 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);
} }
} }
/// <summary> /// <summary>
/// Stop this proxy server /// Stop this proxy server
/// </summary> /// </summary>
public void Stop() public void Stop()
{ {
...@@ -499,7 +522,8 @@ namespace Titanium.Web.Proxy ...@@ -499,7 +522,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)
{ {
...@@ -520,20 +544,7 @@ namespace Titanium.Web.Proxy ...@@ -520,20 +544,7 @@ namespace Titanium.Web.Proxy
} }
/// <summary> /// <summary>
/// Dispose Proxy. /// Listen on the given end point on local machine
/// </summary>
public void Dispose()
{
if (ProxyRunning)
{
Stop();
}
CertificateManager?.Dispose();
}
/// <summary>
/// Listen on the given end point on local machine
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
private void Listen(ProxyEndPoint endPoint) private void Listen(ProxyEndPoint endPoint)
...@@ -550,7 +561,8 @@ namespace Titanium.Web.Proxy ...@@ -550,7 +561,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;
...@@ -558,13 +570,16 @@ namespace Titanium.Web.Proxy ...@@ -558,13 +570,16 @@ namespace Titanium.Web.Proxy
} }
/// <summary> /// <summary>
/// Verifiy if its safe to set this end point as System proxy /// Verifiy if its safe to set this end point as System proxy
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
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");
...@@ -577,10 +592,13 @@ namespace Titanium.Web.Proxy ...@@ -577,10 +592,13 @@ 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);
...@@ -588,7 +606,7 @@ namespace Titanium.Web.Proxy ...@@ -588,7 +606,7 @@ namespace Titanium.Web.Proxy
} }
/// <summary> /// <summary>
/// When a connection is received from client act /// When a connection is received from client act
/// </summary> /// </summary>
/// <param name="asyn"></param> /// <param name="asyn"></param>
private void OnAcceptConnection(IAsyncResult asyn) private void OnAcceptConnection(IAsyncResult asyn)
...@@ -616,10 +634,7 @@ namespace Titanium.Web.Proxy ...@@ -616,10 +634,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.
...@@ -652,7 +667,7 @@ namespace Titanium.Web.Proxy ...@@ -652,7 +667,7 @@ namespace Titanium.Web.Proxy
} }
/// <summary> /// <summary>
/// Quit listening on the given end point /// Quit listening on the given end point
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
private void QuitListen(ProxyEndPoint endPoint) private void QuitListen(ProxyEndPoint endPoint)
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended.Network; using StreamExtended.Network;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
...@@ -11,23 +12,24 @@ using Titanium.Web.Proxy.Helpers; ...@@ -11,23 +12,24 @@ using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.Tcp; using Titanium.Web.Proxy.Network.Tcp;
using System.Threading;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
/// <summary> /// <summary>
/// Handle the request /// Handle the request
/// </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> /// <summary>
/// This is the core request handler method for a particular connection from client /// This is the core request handler method for a particular connection from client
/// Will create new session (request/response) sequence until /// Will create new session (request/response) sequence until
/// client/server abruptly terminates connection or by normal HTTP termination /// client/server abruptly terminates connection or by normal HTTP termination
/// </summary> /// </summary>
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
...@@ -40,8 +42,10 @@ namespace Titanium.Web.Proxy ...@@ -40,8 +42,10 @@ namespace Titanium.Web.Proxy
/// <param name="isTransparentEndPoint"></param> /// <param name="isTransparentEndPoint"></param>
/// <returns></returns> /// <returns></returns>
private async Task HandleHttpSessionRequest(ProxyEndPoint endPoint, TcpClient client, private async Task HandleHttpSessionRequest(ProxyEndPoint endPoint, TcpClient client,
CustomBufferedStream clientStream, CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, CustomBufferedStream clientStream, CustomBinaryReader clientStreamReader,
CancellationTokenSource cancellationTokenSource, string httpsConnectHostname, ConnectRequest connectRequest, bool isTransparentEndPoint = false) HttpResponseWriter clientStreamWriter,
CancellationTokenSource cancellationTokenSource, string httpsConnectHostname, ConnectRequest connectRequest,
bool isTransparentEndPoint = false)
{ {
TcpConnection connection = null; TcpConnection connection = null;
...@@ -68,7 +72,8 @@ namespace Titanium.Web.Proxy ...@@ -68,7 +72,8 @@ 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);
...@@ -94,7 +99,8 @@ namespace Titanium.Web.Proxy ...@@ -94,7 +99,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);
...@@ -116,7 +122,8 @@ namespace Titanium.Web.Proxy ...@@ -116,7 +122,8 @@ 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);
...@@ -163,9 +170,10 @@ namespace Titanium.Web.Proxy ...@@ -163,9 +170,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;
...@@ -184,7 +192,8 @@ namespace Titanium.Web.Proxy ...@@ -184,7 +192,8 @@ namespace Titanium.Web.Proxy
await connection.StreamWriter.WriteHeadersAsync(request.Headers); await connection.StreamWriter.WriteHeadersAsync(request.Headers);
string httpStatus = await connection.StreamReader.ReadLineAsync(); string httpStatus = await connection.StreamReader.ReadLineAsync();
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;
...@@ -216,7 +225,6 @@ namespace Titanium.Web.Proxy ...@@ -216,7 +225,6 @@ namespace Titanium.Web.Proxy
if (args.WebSession.ServerConnection == null) if (args.WebSession.ServerConnection == null)
{ {
//server connection was closed
return; return;
} }
...@@ -246,7 +254,6 @@ namespace Titanium.Web.Proxy ...@@ -246,7 +254,6 @@ namespace Titanium.Web.Proxy
await InvokeAfterResponse(args); await InvokeAfterResponse(args);
args.Dispose(); args.Dispose();
} }
} }
} }
finally finally
...@@ -256,7 +263,7 @@ namespace Titanium.Web.Proxy ...@@ -256,7 +263,7 @@ namespace Titanium.Web.Proxy
} }
/// <summary> /// <summary>
/// Handle a specific session (request/response sequence) /// Handle a specific session (request/response sequence)
/// </summary> /// </summary>
/// <param name="connection"></param> /// <param name="connection"></param>
/// <param name="args"></param> /// <param name="args"></param>
...@@ -285,12 +292,14 @@ namespace Titanium.Web.Proxy ...@@ -285,12 +292,14 @@ 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,
(int)HttpStatusCode.Continue, "Continue");
await clientStreamWriter.WriteLineAsync(); await clientStreamWriter.WriteLineAsync();
} }
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,
(int)HttpStatusCode.ExpectationFailed, "Expectation Failed");
await clientStreamWriter.WriteLineAsync(); await clientStreamWriter.WriteLineAsync();
} }
} }
...@@ -305,7 +314,6 @@ namespace Titanium.Web.Proxy ...@@ -305,7 +314,6 @@ namespace Titanium.Web.Proxy
//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;
...@@ -315,7 +323,6 @@ namespace Titanium.Web.Proxy ...@@ -315,7 +323,6 @@ namespace Titanium.Web.Proxy
{ {
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;
...@@ -338,7 +345,7 @@ namespace Titanium.Web.Proxy ...@@ -338,7 +345,7 @@ namespace Titanium.Web.Proxy
} }
/// <summary> /// <summary>
/// Create a Server Connection /// Create a Server Connection
/// </summary> /// </summary>
/// <param name="args"></param> /// <param name="args"></param>
/// <param name="isConnect"></param> /// <param name="isConnect"></param>
...@@ -365,7 +372,7 @@ namespace Titanium.Web.Proxy ...@@ -365,7 +372,7 @@ namespace Titanium.Web.Proxy
} }
/// <summary> /// <summary>
/// prepare the request headers so that we can avoid encodings not parsable by this proxy /// prepare the request headers so that we can avoid encodings not parsable by this proxy
/// </summary> /// </summary>
/// <param name="requestHeaders"></param> /// <param name="requestHeaders"></param>
private void PrepareRequestHeaders(HeaderCollection requestHeaders) private void PrepareRequestHeaders(HeaderCollection requestHeaders)
......
...@@ -9,12 +9,12 @@ using Titanium.Web.Proxy.Network.WinAuth.Security; ...@@ -9,12 +9,12 @@ using Titanium.Web.Proxy.Network.WinAuth.Security;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
/// <summary> /// <summary>
/// Handle the response from server /// Handle the response from server
/// </summary> /// </summary>
partial class ProxyServer partial class ProxyServer
{ {
/// <summary> /// <summary>
/// Called asynchronously when a request was successfully and we received the response /// Called asynchronously when a request was successfully and we received the response
/// </summary> /// </summary>
/// <param name="args"></param> /// <param name="args"></param>
/// <returns>true if client/server connection was terminated (and disposed) </returns> /// <returns>true if client/server connection was terminated (and disposed) </returns>
...@@ -87,12 +87,14 @@ namespace Titanium.Web.Proxy ...@@ -87,12 +87,14 @@ 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,
(int)HttpStatusCode.Continue, "Continue");
await clientStreamWriter.WriteLineAsync(); await clientStreamWriter.WriteLineAsync();
} }
else if (response.ExpectationFailed) else if (response.ExpectationFailed)
{ {
await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, (int)HttpStatusCode.ExpectationFailed, "Expectation Failed"); await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion,
(int)HttpStatusCode.ExpectationFailed, "Expectation Failed");
await clientStreamWriter.WriteLineAsync(); await clientStreamWriter.WriteLineAsync();
} }
...@@ -108,7 +110,8 @@ namespace Titanium.Web.Proxy ...@@ -108,7 +110,8 @@ namespace Titanium.Web.Proxy
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,
response.StatusDescription);
await clientStreamWriter.WriteHeadersAsync(response.Headers); await clientStreamWriter.WriteHeadersAsync(response.Headers);
//Write body if exists //Write body if exists
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
namespace Titanium.Web.Proxy.Shared namespace Titanium.Web.Proxy.Shared
{ {
/// <summary> /// <summary>
/// Literals shared by Proxy Server /// Literals shared by Proxy Server
/// </summary> /// </summary>
internal class ProxyConstants internal class ProxyConstants
{ {
...@@ -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);
} }
} }
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
using System.Net.Security; using System.Net.Security;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Authentication; using System.Security.Authentication;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended; using StreamExtended;
using StreamExtended.Helpers; using StreamExtended.Helpers;
...@@ -10,16 +11,14 @@ using Titanium.Web.Proxy.EventArguments; ...@@ -10,16 +11,14 @@ using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using System.Threading;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
partial class ProxyServer partial class ProxyServer
{ {
/// <summary> /// <summary>
/// This is called when this proxy acts as a reverse proxy (like a real http server) /// 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 /// So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
/// <param name="tcpClient"></param> /// <param name="tcpClient"></param>
...@@ -32,12 +31,12 @@ namespace Titanium.Web.Proxy ...@@ -32,12 +31,12 @@ namespace Titanium.Web.Proxy
var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize); var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
try try
{ {
var clientHelloInfo = await SslTools.PeekClientHello(clientStream); var clientHelloInfo = await SslTools.PeekClientHello(clientStream);
var isHttps = clientHelloInfo != null; bool isHttps = clientHelloInfo != null;
string httpsHostName = null; string httpsHostName = null;
if (isHttps) if (isHttps)
...@@ -76,7 +75,8 @@ namespace Titanium.Web.Proxy ...@@ -76,7 +75,8 @@ namespace Titanium.Web.Proxy
} }
catch (Exception e) catch (Exception e)
{ {
ExceptionFunc(new Exception($"Could'nt authenticate client '{httpsHostName}' with fake certificate.", e)); ExceptionFunc(new Exception(
$"Could'nt authenticate client '{httpsHostName}' with fake certificate.", e));
sslStream?.Dispose(); sslStream?.Dispose();
return; return;
} }
...@@ -136,4 +136,4 @@ namespace Titanium.Web.Proxy ...@@ -136,4 +136,4 @@ namespace Titanium.Web.Proxy
} }
} }
} }
} }
\ No newline at end of file
...@@ -32,13 +32,13 @@ namespace Titanium.Web.Proxy ...@@ -32,13 +32,13 @@ namespace Titanium.Web.Proxy
}; };
/// <summary> /// <summary>
/// Handle windows NTLM authentication /// Handle windows NTLM authentication
/// Can expand this for Kerberos in future /// Can expand this for Kerberos in future
/// Note: NTLM/Kerberos cannot do a man in middle operation /// Note: NTLM/Kerberos cannot do a man in middle operation
/// we do for HTTPS requests. /// we do for HTTPS requests.
/// As such we will be sending local credentials of current /// As such we will be sending local credentials of current
/// User to server to authenticate requests. /// User to server to authenticate requests.
/// To disable this set ProxyServer.EnableWinAuth to false /// To disable this set ProxyServer.EnableWinAuth to false
/// </summary> /// </summary>
internal async Task Handle401UnAuthorized(SessionEventArgs args) internal async Task Handle401UnAuthorized(SessionEventArgs args)
{ {
...@@ -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
...@@ -87,7 +88,8 @@ namespace Titanium.Web.Proxy ...@@ -87,7 +88,8 @@ namespace Titanium.Web.Proxy
{ {
string scheme = authSchemes.Contains(authHeader.Value.ToLower()) ? authHeader.Value.ToLower() : null; string scheme = authSchemes.Contains(authHeader.Value.ToLower()) ? authHeader.Value.ToLower() : 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,8 +122,9 @@ namespace Titanium.Web.Proxy ...@@ -120,8 +122,9 @@ 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.Length > x.Length + 1); authHeader.Value.StartsWith(x, StringComparison.OrdinalIgnoreCase) &&
authHeader.Value.Length > x.Length + 1);
string serverToken = authHeader.Value.Substring(scheme.Length + 1); string serverToken = authHeader.Value.Substring(scheme.Length + 1);
string clientToken = WinAuthHandler.GetFinalAuthToken(request.Host, serverToken, args.Id); string clientToken = WinAuthHandler.GetFinalAuthToken(request.Host, serverToken, args.Id);
...@@ -148,7 +151,7 @@ namespace Titanium.Web.Proxy ...@@ -148,7 +151,7 @@ namespace Titanium.Web.Proxy
} }
/// <summary> /// <summary>
/// Rewrites the response body for failed authentication /// Rewrites the response body for failed authentication
/// </summary> /// </summary>
/// <param name="args"></param> /// <param name="args"></param>
/// <returns></returns> /// <returns></returns>
...@@ -157,32 +160,35 @@ namespace Titanium.Web.Proxy ...@@ -157,32 +160,35 @@ 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 =
args.ProxyClient.TcpClient.Client.LocalEndPoint + "<div class=\"inserted-by-proxy\"><h2>NTLM authentication through Titanium.Web.Proxy (" +
") failed. Please check credentials.</h2></div>"; args.ProxyClient.TcpClient.Client.LocalEndPoint +
string originalErrorMessage = "<div class=\"inserted-by-proxy\"><h3>Response from remote web server below.</h3></div><br/>"; ") failed. Please check credentials.</h2></div>";
string originalErrorMessage =
"<div class=\"inserted-by-proxy\"><h3>Response from remote web server below.</h3></div><br/>";
string body = await args.GetResponseBodyAsString(); string body = await args.GetResponseBodyAsString();
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 =
"<html xmlns=\"http://www.w3.org/1999/xhtml\">" + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">" +
"<body>" + "<html xmlns=\"http://www.w3.org/1999/xhtml\">" +
authErrorMessage + "<body>" +
"</body>" + authErrorMessage +
"</html>"; "</body>" +
"</html>";
} }
args.SetResponseBodyString(body); args.SetResponseBodyString(body);
......
<?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.1.4" targetFramework="net45" />
<package id="StreamExtended" version="1.0.141-beta" targetFramework="net45" /> <package id="StreamExtended" version="1.0.141-beta" targetFramework="net45" />
......
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