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
public partial class ProxyServer
{
/// <summary>
/// Call back to override server certificate validation
/// Call back to override server certificate validation
/// </summary>
/// <param name="sender"></param>
/// <param name="certificate"></param>
/// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param>
/// <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 (ServerCertificateValidationCallback != null)
......@@ -44,7 +45,7 @@ namespace Titanium.Web.Proxy
}
/// <summary>
/// Call back to select client certificate used for mutual authentication
/// Call back to select client certificate used for mutual authentication
/// </summary>
/// <param name="sender"></param>
/// <param name="targetHost"></param>
......@@ -52,14 +53,15 @@ namespace Titanium.Web.Proxy
/// <param name="remoteCertificate"></param>
/// <param name="acceptableIssuers"></param>
/// <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 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)
{
string issuer = certificate.Issuer;
......
......@@ -4,7 +4,7 @@ using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Compression
{
/// <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>
internal static class CompressionFactory
{
......
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression
{
/// <summary>
/// Concrete implementation of deflate compression
/// Concrete implementation of deflate compression
/// </summary>
internal class DeflateCompression : ICompression
{
......
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression
{
/// <summary>
/// concreate implementation of gzip compression
/// concreate implementation of gzip compression
/// </summary>
internal class GZipCompression : ICompression
{
......
using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression
{
/// <summary>
/// An inteface for http compression
/// An inteface for http compression
/// </summary>
interface ICompression
internal interface ICompression
{
Stream GetStream(Stream stream);
}
......
......@@ -4,13 +4,15 @@ using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Decompression
{
/// <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>
internal class DecompressionFactory
{
//cache
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)
{
......
using StreamExtended.Helpers;
using System.IO;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Decompression
{
/// <summary>
/// concrete implementation of deflate de-compression
/// concrete implementation of deflate de-compression
/// </summary>
internal class DeflateDecompression : IDecompression
{
......
using StreamExtended.Helpers;
using System.IO;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Decompression
{
/// <summary>
/// concrete implementation of gzip de-compression
/// concrete implementation of gzip de-compression
/// </summary>
internal class GZipDecompression : IDecompression
{
......
using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Decompression
{
/// <summary>
/// An interface for decompression
/// An interface for decompression
/// </summary>
internal interface IDecompression
{
......
......@@ -3,4 +3,4 @@
namespace Titanium.Web.Proxy.EventArguments
{
public delegate Task AsyncEventHandler<TEventArgs>(object sender, TEventArgs e);
}
\ No newline at end of file
}
......@@ -4,37 +4,37 @@ using System.Security.Cryptography.X509Certificates;
namespace Titanium.Web.Proxy.EventArguments
{
/// <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>
public class CertificateSelectionEventArgs : EventArgs
{
/// <summary>
/// Sender object.
/// Sender object.
/// </summary>
public object Sender { get; internal set; }
/// <summary>
/// Target host.
/// Target host.
/// </summary>
public string TargetHost { get; internal set; }
/// <summary>
/// Local certificates.
/// Local certificates.
/// </summary>
public X509CertificateCollection LocalCertificates { get; internal set; }
/// <summary>
/// Remote certificate.
/// Remote certificate.
/// </summary>
public X509Certificate RemoteCertificate { get; internal set; }
/// <summary>
/// Acceptable issuers.
/// Acceptable issuers.
/// </summary>
public string[] AcceptableIssuers { get; internal set; }
/// <summary>
/// Client Certificate.
/// Client Certificate.
/// </summary>
public X509Certificate ClientCertificate { get; set; }
}
......
......@@ -5,27 +5,27 @@ using System.Security.Cryptography.X509Certificates;
namespace Titanium.Web.Proxy.EventArguments
{
/// <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>
public class CertificateValidationEventArgs : EventArgs
{
/// <summary>
/// Certificate
/// Certificate
/// </summary>
public X509Certificate Certificate { get; internal set; }
/// <summary>
/// Certificate chain
/// Certificate chain
/// </summary>
public X509Chain Chain { get; internal set; }
/// <summary>
/// SSL policy errors.
/// SSL policy errors.
/// </summary>
public SslPolicyErrors SslPolicyErrors { get; internal set; }
/// <summary>
/// is a valid certificate?
/// is a valid certificate?
/// </summary>
public bool IsValid { get; set; }
}
......
......@@ -4,17 +4,17 @@ namespace Titanium.Web.Proxy.EventArguments
{
public class DataEventArgs : EventArgs
{
public byte[] Buffer { get; }
public int Offset { get; }
public int Count { get; }
internal DataEventArgs(byte[] buffer, int offset, int count)
{
Buffer = buffer;
Offset = offset;
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
{
internal class LimitedStream : Stream
{
private readonly CustomBufferedStream baseStream;
private readonly CustomBinaryReader baseReader;
private readonly CustomBufferedStream baseStream;
private readonly bool isChunked;
private long bytesRemaining;
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.baseReader = baseReader;
this.isChunked = isChunked;
bytesRemaining = isChunked
? 0
: contentLength == -1
? long.MaxValue
bytesRemaining = isChunked
? 0
: contentLength == -1
? long.MaxValue
: contentLength;
}
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => throw new NotSupportedException();
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
private void GetNextChunk()
{
if (readChunkTrail)
......@@ -42,7 +57,6 @@ namespace Titanium.Web.Proxy.EventArguments
int idx = chunkHead.IndexOf(";");
if (idx >= 0)
{
// remove chunk extension
chunkHead = chunkHead.Substring(0, idx);
}
......@@ -133,19 +147,5 @@ namespace Titanium.Web.Proxy.EventArguments
{
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
{
public class MultipartRequestPartSentEventArgs : EventArgs
{
public string Boundary { get; }
public HeaderCollection Headers { get; }
public MultipartRequestPartSentEventArgs(string boundary, HeaderCollection headers)
{
Boundary = boundary;
Headers = headers;
}
public string Boundary { get; }
public HeaderCollection Headers { get; }
}
}
\ No newline at end of file
}
......@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended.Helpers;
using StreamExtended.Network;
......@@ -10,8 +11,6 @@ using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Http.Responses;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using System.Threading;
namespace Titanium.Web.Proxy.EventArguments
{
......@@ -30,6 +29,21 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
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;
/// <summary>
......@@ -54,21 +68,6 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
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)
{
return isRequest ? ProxyClient.ClientStream : WebSession.ServerConnection.Stream;
......@@ -476,7 +475,7 @@ namespace Titanium.Web.Proxy.EventArguments
{
response.Headers.AddHeaders(headers);
}
response.HttpVersion = WebSession.Request.HttpVersion;
response.Body = response.Encoding.GetBytes(html ?? string.Empty);
......
......@@ -9,15 +9,15 @@ using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.EventArguments
{
/// <summary>
/// Holds info related to a single proxy session (single request/response sequence)
/// A proxy session is bounded to a single connection from client
/// A proxy session ends when client terminates connection to proxy
/// or when server terminates connection from proxy
/// Holds info related to a single proxy session (single request/response sequence)
/// A proxy session is bounded to a single connection from client
/// A proxy session ends when client terminates connection to proxy
/// or when server terminates connection from proxy
/// </summary>
public class SessionEventArgsBase : EventArgs, IDisposable
{
/// <summary>
/// Size of Buffers used by this object
/// Size of Buffers used by this object
/// </summary>
protected readonly int BufferSize;
......@@ -26,41 +26,78 @@ namespace Titanium.Web.Proxy.EventArguments
internal CancellationTokenSource cancellationTokenSource;
/// <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>
internal ProxyClient ProxyClient { get; }
/// <summary>
/// Returns a unique Id for this request/response session
/// same as RequestId of WebSession
/// Returns a unique Id for this request/response session
/// same as RequestId of WebSession
/// </summary>
public Guid Id => WebSession.RequestId;
/// <summary>
/// Does this session uses SSL
/// Does this session uses SSL
/// </summary>
public bool IsHttps => WebSession.Request.IsHttps;
/// <summary>
/// Client End Point.
/// Client End Point.
/// </summary>
public IPEndPoint ClientEndPoint => (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint;
/// <summary>
/// A web session corresponding to a single request/response sequence
/// within a proxy connection
/// A web session corresponding to a single request/response sequence
/// within a proxy connection
/// </summary>
public HttpWebClient WebSession { get; }
/// <summary>
/// Are we using a custom upstream HTTP(S) proxy?
/// Are we using a custom upstream HTTP(S) proxy?
/// </summary>
public ExternalProxy CustomUpStreamProxyUsed { get; internal set; }
public event EventHandler<DataEventArgs> DataSent;
public event EventHandler<DataEventArgs> DataReceived;
public ProxyEndPoint LocalEndPoint { get; }
public bool IsTransparent => LocalEndPoint is TransparentProxyEndPoint;
......@@ -68,45 +105,22 @@ namespace Titanium.Web.Proxy.EventArguments
public Exception Exception { get; internal set; }
/// <summary>
/// Constructor to initialize the proxy
/// implement any cleanup here
/// </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)
public virtual void Dispose()
{
this.BufferSize = bufferSize;
this.ExceptionFunc = exceptionFunc;
this.cancellationTokenSource = cancellationTokenSource;
ProxyClient = new ProxyClient();
WebSession = new HttpWebClient(bufferSize, request);
LocalEndPoint = endPoint;
CustomUpStreamProxyUsed = null;
WebSession.ProcessId = new Lazy<int>(() =>
{
if (RunTime.IsWindows)
{
var remoteEndPoint = (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint;
DataSent = null;
DataReceived = null;
Exception = null;
//If client is localhost get the process id
if (NetworkHelper.IsLocalIpAddress(remoteEndPoint.Address))
{
return NetworkHelper.GetProcessIdFromPort(remoteEndPoint.Port, endPoint.IpV6Enabled);
}
WebSession.FinishSession();
}
//can't access process Id of remote request from remote machine
return -1;
}
public event EventHandler<DataEventArgs> DataSent;
throw new PlatformNotSupportedException();
});
}
public event EventHandler<DataEventArgs> DataReceived;
internal void OnDataSent(byte[] buffer, int offset, int count)
{
......@@ -133,24 +147,11 @@ namespace Titanium.Web.Proxy.EventArguments
}
/// <summary>
/// Terminates the session abruptly by terminating client/server connections
/// Terminates the session abruptly by terminating client/server connections
/// </summary>
public void TerminateSession()
{
cancellationTokenSource.Cancel();
}
/// <summary>
/// implement any cleanup here
/// </summary>
public virtual void Dispose()
{
CustomUpStreamProxyUsed = null;
DataSent = null;
DataReceived = null;
Exception = null;
WebSession.FinishSession();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.EventArguments
namespace Titanium.Web.Proxy.EventArguments
{
internal enum TransformationMode
{
None,
/// <summary>
/// Removes the chunked encoding
/// Removes the chunked encoding
/// </summary>
RemoveChunked,
/// <summary>
/// Uncompress the body (this also removes the chunked encoding if exists)
/// Uncompress the body (this also removes the chunked encoding if exists)
/// </summary>
Uncompress,
Uncompress
}
}
using System;
using System.Threading;
using System.Threading;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
......@@ -7,18 +6,19 @@ namespace Titanium.Web.Proxy.EventArguments
{
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;
/// <summary>
/// Denies the connect request with a Forbidden status
/// Denies the connect request with a Forbidden status
/// </summary>
public bool DenyConnect { get; 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;
namespace Titanium.Web.Proxy
{
public delegate void ExceptionHandler(Exception exception);
}
\ No newline at end of file
}
namespace Titanium.Web.Proxy.Exceptions
{
/// <summary>
/// An expception thrown when body is unexpectedly empty
/// An expception thrown when body is unexpectedly empty
/// </summary>
public class BodyNotFoundException : ProxyException
{
/// <summary>
/// Constructor.
/// Constructor.
/// </summary>
/// <param name="message"></param>
internal BodyNotFoundException(string message) : base(message)
......
......@@ -6,18 +6,19 @@ using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Exceptions
{
/// <summary>
/// Proxy authorization exception
/// Proxy authorization exception
/// </summary>
public class ProxyAuthorizationException : ProxyException
{
/// <summary>
/// Instantiate new instance
/// Instantiate new instance
/// </summary>
/// <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="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;
Headers = headers;
......@@ -26,7 +27,7 @@ namespace Titanium.Web.Proxy.Exceptions
public SessionEventArgsBase Session { get; }
/// <summary>
/// Headers associated with the authorization exception
/// Headers associated with the authorization exception
/// </summary>
public IEnumerable<HttpHeader> Headers { get; }
}
......
......@@ -3,12 +3,12 @@
namespace Titanium.Web.Proxy.Exceptions
{
/// <summary>
/// Base class exception associated with this proxy implementation
/// Base class exception associated with this proxy implementation
/// </summary>
public abstract class ProxyException : Exception
{
/// <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>
/// <param name="message">Exception message</param>
protected ProxyException(string message) : base(message)
......@@ -16,7 +16,7 @@ namespace Titanium.Web.Proxy.Exceptions
}
/// <summary>
/// Instantiate this exception - must be invoked by derived classes' constructors
/// Instantiate this exception - must be invoked by derived classes' constructors
/// </summary>
/// <param name="message">Excception message</param>
/// <param name="innerException">Inner exception associated</param>
......
......@@ -4,26 +4,27 @@ using Titanium.Web.Proxy.EventArguments;
namespace Titanium.Web.Proxy.Exceptions
{
/// <summary>
/// Proxy HTTP exception
/// Proxy HTTP exception
/// </summary>
public class ProxyHttpException : ProxyException
{
/// <summary>
/// Instantiate new instance
/// Instantiate new instance
/// </summary>
/// <param name="message">Message for this exception</param>
/// <param name="innerException">Associated inner exception</param>
/// <param name="sessionEventArgs">Instance of <see cref="EventArguments.SessionEventArgs"/> associated to the exception</param>
internal ProxyHttpException(string message, Exception innerException, SessionEventArgs sessionEventArgs) : base(message, innerException)
/// <param name="sessionEventArgs">Instance of <see cref="EventArguments.SessionEventArgs" /> associated to the exception</param>
internal ProxyHttpException(string message, Exception innerException, SessionEventArgs sessionEventArgs) : base(
message, innerException)
{
SessionEventArgs = sessionEventArgs;
}
/// <summary>
/// Gets session info associated to the exception
/// Gets session info associated to the exception
/// </summary>
/// <remarks>
/// This object should not be edited
/// This object should not be edited
/// </remarks>
public SessionEventArgs SessionEventArgs { get; }
}
......
......@@ -3,6 +3,7 @@ using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended;
using StreamExtended.Helpers;
......@@ -12,15 +13,14 @@ using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
using System.Threading;
namespace Titanium.Web.Proxy
{
partial class ProxyServer
{
/// <summary>
/// This is called when client is aware of proxy
/// So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
/// This is called when client is aware of proxy
/// So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
/// </summary>
/// <param name="endPoint"></param>
/// <param name="tcpClient"></param>
......@@ -57,12 +57,13 @@ namespace Titanium.Web.Proxy
{
RequestUri = httpRemoteUri,
OriginalUrl = httpUrl,
HttpVersion = version,
HttpVersion = version
};
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.ClientStream = clientStream;
......@@ -79,7 +80,7 @@ namespace Titanium.Web.Proxy
{
HttpVersion = HttpHeader.Version11,
StatusCode = (int)HttpStatusCode.Forbidden,
StatusDescription = "Forbidden",
StatusDescription = "Forbidden"
};
}
......@@ -128,7 +129,8 @@ namespace Titanium.Web.Proxy
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
await sslStream.AuthenticateAsServerAsync(certificate, false, SupportedSslProtocols, false);
......@@ -142,14 +144,14 @@ namespace Titanium.Web.Proxy
}
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();
return;
}
if (await HttpHelper.IsConnectMethod(clientStream) == -1)
{
// It can be for example some Google (Cloude Messaging for Chrome) magic
decryptSsl = false;
}
}
......@@ -200,7 +202,8 @@ namespace Titanium.Web.Proxy
}
//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)
{
......@@ -228,7 +231,5 @@ namespace Titanium.Web.Proxy
}
}
}
}
}
......@@ -3,12 +3,12 @@
namespace Titanium.Web.Proxy.Extensions
{
/// <summary>
/// Extension methods for Byte Arrays.
/// Extension methods for Byte Arrays.
/// </summary>
internal static class ByteArrayExtensions
{
/// <summary>
/// Get the sub array from byte of data
/// Get the sub array from byte of data
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="data"></param>
......
......@@ -6,8 +6,8 @@ namespace Titanium.Web.Proxy.Extensions
{
internal static class FuncExtensions
{
public static async Task InvokeAsync<T>(this AsyncEventHandler<T> callback, object sender, T args, ExceptionHandler exceptionFunc)
public static async Task InvokeAsync<T>(this AsyncEventHandler<T> callback, object sender, T args,
ExceptionHandler exceptionFunc)
{
var invocationList = callback.GetInvocationList();
......@@ -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
{
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StreamExtended;
using StreamExtended;
namespace Titanium.Web.Proxy.Extensions
{
static class SslExtensions
internal static class SslExtensions
{
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;
}
......
......@@ -7,40 +7,43 @@ using StreamExtended.Helpers;
namespace Titanium.Web.Proxy.Extensions
{
/// <summary>
/// Extensions used for Stream and CustomBinaryReader objects
/// Extensions used for Stream and CustomBinaryReader objects
/// </summary>
internal static class StreamExtensions
{
/// <summary>
/// Copy streams asynchronously
/// Copy streams asynchronously
/// </summary>
/// <param name="input"></param>
/// <param name="output"></param>
/// <param name="onCopy"></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);
}
/// <summary>
/// Copy streams asynchronously
/// Copy streams asynchronously
/// </summary>
/// <param name="input"></param>
/// <param name="output"></param>
/// <param name="onCopy"></param>
/// <param name="bufferSize"></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
{
while (!cancellationToken.IsCancellationRequested)
{
// cancellation is not working on Socket ReadAsync
// 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;
if ((bytesRead = num) != 0 && !cancellationToken.IsCancellationRequested)
{
......@@ -66,7 +69,7 @@ namespace Titanium.Web.Proxy.Extensions
{
if (task != await Task.WhenAny(task, tcs.Task))
{
return default(T);
return default;
}
}
......
......@@ -17,13 +17,14 @@ namespace Titanium.Web.Proxy.Extensions
var method = property.GetMethod;
if (method != null && method.ReturnType == typeof(bool))
{
socketCleanedUpGetter = (Func<Socket, bool>)Delegate.CreateDelegate(typeof(Func<Socket, bool>), method);
socketCleanedUpGetter =
(Func<Socket, bool>)Delegate.CreateDelegate(typeof(Func<Socket, bool>), method);
}
}
}
/// <summary>
/// Gets the local port from a native TCP row object.
/// Gets the local port from a native TCP row object.
/// </summary>
/// <param name="tcpRow">The TCP row.</param>
/// <returns>The local port</returns>
......@@ -33,13 +34,14 @@ namespace Titanium.Web.Proxy.Extensions
}
/// <summary>
/// Gets the remote port from a native TCP row object.
/// Gets the remote port from a native TCP row object.
/// </summary>
/// <param name="tcpRow">The TCP row.</param>
/// <returns>The remote port</returns>
internal static int GetRemotePort(this NativeMethods.TcpRow tcpRow)
{
return (tcpRow.remotePort1 << 8) + tcpRow.remotePort2 + (tcpRow.remotePort3 << 24) + (tcpRow.remotePort4 << 16);
return (tcpRow.remotePort1 << 8) + tcpRow.remotePort2 + (tcpRow.remotePort3 << 24) +
(tcpRow.remotePort4 << 16);
}
internal static void CloseSocket(this TcpClient tcpClient)
......
using StreamExtended.Network;
using System;
using System;
using System.Text;
using System.Threading.Tasks;
using StreamExtended.Network;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
......@@ -13,7 +13,7 @@ namespace Titanium.Web.Proxy.Helpers
private static readonly Encoding defaultEncoding = Encoding.GetEncoding("ISO-8859-1");
/// <summary>
/// Gets the character encoding of request/response from content-type header
/// Gets the character encoding of request/response from content-type header
/// </summary>
/// <param name="contentType"></param>
/// <returns></returns>
......@@ -37,7 +37,6 @@ namespace Titanium.Web.Proxy.Helpers
string value = split[1];
if (value.Equals("x-user-defined", StringComparison.OrdinalIgnoreCase))
{
//todo: what is this?
continue;
}
......@@ -87,9 +86,9 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// Tries to get root domain from a given hostname
/// Adapted from below answer
/// https://stackoverflow.com/questions/16473838/get-domain-name-of-a-url-in-c-sharp-net
/// Tries to get root domain from a given hostname
/// Adapted from below answer
/// https://stackoverflow.com/questions/16473838/get-domain-name-of-a-url-in-c-sharp-net
/// </summary>
/// <param name="hostname"></param>
/// <returns></returns>
......@@ -103,7 +102,7 @@ namespace Titanium.Web.Proxy.Helpers
int idx = hostname.IndexOf(ProxyConstants.DotSplit);
//issue #352
if(hostname.Substring(0, idx).Contains("-"))
if (hostname.Substring(0, idx).Contains("-"))
{
return hostname;
}
......@@ -117,7 +116,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// Determines whether is connect method.
/// Determines whether is connect method.
/// </summary>
/// <param name="clientStream">The client stream.</param>
/// <returns>1: when CONNECT, 0: when valid HTTP method, -1: otherwise</returns>
......@@ -135,14 +134,12 @@ namespace Titanium.Web.Proxy.Helpers
if (b == ' ' && i > 2)
{
// at least 3 letters and a space
return isConnect ? 1 : 0;
}
char ch = (char)b;
if (!char.IsLetter(ch))
{
// non letter or too short
return -1;
}
......
......@@ -2,7 +2,7 @@
namespace Titanium.Web.Proxy.Helpers
{
sealed class HttpRequestWriter : HttpWriter
internal sealed class HttpRequestWriter : HttpWriter
{
public HttpRequestWriter(Stream stream, int bufferSize) : base(stream, bufferSize)
{
......
......@@ -5,14 +5,14 @@ using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Helpers
{
sealed class HttpResponseWriter : HttpWriter
internal sealed class HttpResponseWriter : HttpWriter
{
public HttpResponseWriter(Stream stream, int bufferSize) : base(stream, bufferSize)
{
}
/// <summary>
/// Writes the response.
/// Writes the response.
/// </summary>
/// <param name="response"></param>
/// <param name="flush"></param>
......@@ -24,7 +24,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// Write response status
/// Write response status
/// </summary>
/// <param name="version"></param>
/// <param name="code"></param>
......
......@@ -5,7 +5,6 @@ using System.Text;
using System.Threading.Tasks;
using StreamExtended.Helpers;
using StreamExtended.Network;
using Titanium.Web.Proxy.Compression;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
......@@ -13,14 +12,12 @@ namespace Titanium.Web.Proxy.Helpers
{
internal class HttpWriter : CustomBinaryWriter
{
public int BufferSize { get; }
private readonly char[] charBuffer;
private static readonly byte[] newLine = ProxyConstants.NewLine;
private static readonly Encoder encoder = Encoding.ASCII.GetEncoder();
private readonly char[] charBuffer;
internal HttpWriter(Stream stream, int bufferSize) : base(stream)
{
BufferSize = bufferSize;
......@@ -29,6 +26,8 @@ namespace Titanium.Web.Proxy.Helpers
charBuffer = new char[BufferSize - 1];
}
public int BufferSize { get; }
public Task WriteLineAsync()
{
return WriteAsync(newLine);
......@@ -87,7 +86,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// Write the headers to client
/// Write the headers to client
/// </summary>
/// <param name="headers"></param>
/// <param name="flush"></param>
......@@ -102,7 +101,6 @@ namespace Titanium.Web.Proxy.Helpers
await WriteLineAsync();
if (flush)
{
// flush the stream
await FlushAsync();
}
}
......@@ -112,7 +110,6 @@ namespace Titanium.Web.Proxy.Helpers
await WriteAsync(data, 0, data.Length);
if (flush)
{
// flush the stream
await FlushAsync();
}
}
......@@ -122,13 +119,12 @@ namespace Titanium.Web.Proxy.Helpers
await WriteAsync(data, offset, count);
if (flush)
{
// flush the stream
await FlushAsync();
}
}
/// <summary>
/// Writes the byte array body to the stream; optionally chunked
/// Writes the byte array body to the stream; optionally chunked
/// </summary>
/// <param name="data"></param>
/// <param name="isChunked"></param>
......@@ -144,24 +140,23 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// optionally chunked
/// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// optionally chunked
/// </summary>
/// <param name="streamReader"></param>
/// <param name="isChunked"></param>
/// <param name="contentLength"></param>
/// <param name="onCopy"></param>
/// <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
if (isChunked)
{
//Need to revist, find any potential bugs
//send the body bytes to server in chunks
return CopyBodyChunkedAsync(streamReader, onCopy);
}
//http 1.0 or the stream reader limits the stream
if (contentLength == -1)
{
......@@ -173,7 +168,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// Copies the given input bytes to output stream chunked
/// Copies the given input bytes to output stream chunked
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
......@@ -191,7 +186,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// Copies the streams chunked
/// Copies the streams chunked
/// </summary>
/// <param name="reader"></param>
/// <param name="onCopy"></param>
......@@ -204,7 +199,6 @@ namespace Titanium.Web.Proxy.Helpers
int idx = chunkHead.IndexOf(";");
if (idx >= 0)
{
// remove chunk extension
chunkHead = chunkHead.Substring(0, idx);
}
......@@ -230,7 +224,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// Copies the specified bytes to the stream from the input stream
/// Copies the specified bytes to the stream from the input stream
/// </summary>
/// <param name="reader"></param>
/// <param name="count"></param>
......@@ -264,7 +258,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// Writes the request/response headers and body.
/// Writes the request/response headers and body.
/// </summary>
/// <param name="requestResponse"></param>
/// <param name="flush"></param>
......
......@@ -5,15 +5,16 @@ namespace Titanium.Web.Proxy.Helpers
{
internal partial class NativeMethods
{
// Keeps it from getting garbage collected
internal static ConsoleEventDelegate Handler;
[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")]
internal static extern IntPtr GetConsoleWindow();
// Keeps it from getting garbage collected
internal static ConsoleEventDelegate Handler;
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetConsoleCtrlHandler(ConsoleEventDelegate callback, bool add);
......
......@@ -9,6 +9,13 @@ namespace Titanium.Web.Proxy.Helpers
internal const int AfInet = 2;
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
{
BasicListener,
......@@ -19,11 +26,11 @@ namespace Titanium.Web.Proxy.Helpers
OwnerPidAll,
OwnerModuleListener,
OwnerModuleConnections,
OwnerModuleAll,
OwnerModuleAll
}
/// <summary>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa366921.aspx"/>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa366921.aspx" />
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct TcpTable
......@@ -33,7 +40,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa366913.aspx" />
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct TcpRow
......@@ -51,11 +58,5 @@ namespace Titanium.Web.Proxy.Helpers
public byte remotePort4;
public int owningPid;
}
/// <summary>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/>
/// </summary>
[DllImport("iphlpapi.dll", SetLastError = true)]
internal static extern uint GetExtendedTcpTable(IntPtr tcpTable, ref int size, bool sort, int ipVersion, int tableClass, int reserved);
}
}
......@@ -26,8 +26,8 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// Adapated from below link
/// http://stackoverflow.com/questions/11834091/how-to-check-if-localhost
/// Adapated from below link
/// http://stackoverflow.com/questions/11834091/how-to-check-if-localhost
/// </summary>
/// <param name="address"></param>
/// <returns></returns>
......@@ -55,7 +55,9 @@ namespace Titanium.Web.Proxy.Helpers
localhost = Dns.GetHostEntry(Dns.GetHostName());
if (IPAddress.TryParse(hostName, out var ipAddress))
{
isLocalhost = localhost.AddressList.Any(x => x.Equals(ipAddress));
}
if (!isLocalhost)
{
......
......@@ -8,25 +8,8 @@ namespace Titanium.Web.Proxy.Helpers
{
internal class ProxyInfo
{
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; }
public ProxyInfo(bool? autoDetect, string autoConfigUrl, int? proxyEnable, string proxyServer, string proxyOverride)
public ProxyInfo(bool? autoDetect, string autoConfigUrl, int? proxyEnable, string proxyServer,
string proxyOverride)
{
AutoDetect = autoDetect;
AutoConfigUrl = autoConfigUrl;
......@@ -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)
{
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 rawString1;
string empty2;
......@@ -92,10 +94,14 @@ namespace Titanium.Web.Proxy.Helpers
string str2 = ConvertRegexReservedChars(rawString1);
string str3 = ConvertRegexReservedChars(empty2);
if (str1 == string.Empty)
{
str1 = "(?:.*://)?";
}
if (str3 == string.Empty)
{
str3 = "(?::[0-9]{1,5})?";
}
return "^" + str1 + str2 + str3 + "$";
}
......@@ -103,15 +109,21 @@ namespace Titanium.Web.Proxy.Helpers
private static string ConvertRegexReservedChars(string rawString)
{
if (rawString.Length == 0)
{
return rawString;
}
var stringBuilder = new StringBuilder();
foreach (char ch in rawString)
{
if ("#$()+.?[\\^{|".IndexOf(ch) != -1)
{
stringBuilder.Append('\\');
}
else if (ch == 42)
{
stringBuilder.Append('.');
}
stringBuilder.Append(ch);
}
......@@ -131,7 +143,8 @@ namespace Titanium.Web.Proxy.Helpers
{
protocolType = ProxyProtocolType.Http;
}
else if (protocolTypeStr.Equals(Proxy.ProxyServer.UriSchemeHttps, StringComparison.InvariantCultureIgnoreCase))
else if (protocolTypeStr.Equals(Proxy.ProxyServer.UriSchemeHttps,
StringComparison.InvariantCultureIgnoreCase))
{
protocolType = ProxyProtocolType.Https;
}
......@@ -140,7 +153,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// Parse the system proxy setting values
/// Parse the system proxy setting values
/// </summary>
/// <param name="proxyServerValues"></param>
/// <returns></returns>
......@@ -149,7 +162,9 @@ namespace Titanium.Web.Proxy.Helpers
var result = new List<HttpSystemProxyValue>();
if (string.IsNullOrWhiteSpace(proxyServerValues))
{
return result;
}
var proxyValues = proxyServerValues.Split(';');
......@@ -161,14 +176,16 @@ namespace Titanium.Web.Proxy.Helpers
{
var parsedValue = ParseProxyValue(proxyServerValues);
if (parsedValue != null)
{
result.Add(parsedValue);
}
}
return result;
}
/// <summary>
/// Parses the system proxy setting string
/// Parses the system proxy setting string
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
......@@ -189,7 +206,7 @@ namespace Titanium.Web.Proxy.Helpers
{
HostName = endPointParts[0],
Port = int.Parse(endPointParts[1]),
ProtocolType = protocolType.Value,
ProtocolType = protocolType.Value
};
}
}
......
......@@ -4,26 +4,27 @@ using System.Runtime.InteropServices;
namespace Titanium.Web.Proxy.Helpers
{
/// <summary>
/// Run time helpers
/// Run time helpers
/// </summary>
internal class RunTime
{
/// <summary>
/// cache for mono runtime check
/// cache for mono runtime check
/// </summary>
/// <returns></returns>
private static readonly Lazy<bool> isRunningOnMono = new Lazy<bool>(() => Type.GetType("Mono.Runtime") != null);
#if NETSTANDARD2_0
/// <summary>
/// cache for Windows platform check
/// </summary>
/// <returns></returns>
/// <summary>
/// cache for Windows platform check
/// </summary>
/// <returns></returns>
private static readonly Lazy<bool> isRunningOnWindows = new Lazy<bool>(() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
#endif
/// <summary>
/// Is running on Mono?
/// Is running on Mono?
/// </summary>
internal static bool IsRunningOnMono => isRunningOnMono.Value;
......
......@@ -9,24 +9,24 @@ namespace Titanium.Web.Proxy.Helpers
public enum ProxyProtocolType
{
/// <summary>
/// The none
/// The none
/// </summary>
None = 0,
/// <summary>
/// HTTP
/// HTTP
/// </summary>
Http = 1,
/// <summary>
/// HTTPS
/// HTTPS
/// </summary>
Https = 2,
/// <summary>
/// Both HTTP and HTTPS
/// Both HTTP and HTTPS
/// </summary>
AllHttp = Http | Https,
AllHttp = Http | Https
}
internal class HttpSystemProxyValue
......@@ -57,7 +57,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// Manage system proxy settings
/// Manage system proxy settings
/// </summary>
internal class SystemProxyManager
{
......@@ -94,7 +94,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// Set the HTTP and/or HTTPS proxy server for current machine
/// Set the HTTP and/or HTTPS proxy server for current machine
/// </summary>
/// <param name="hostname"></param>
/// <param name="port"></param>
......@@ -133,14 +133,15 @@ namespace Titanium.Web.Proxy.Helpers
reg.DeleteValue(regAutoConfigUrl, false);
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();
}
}
/// <summary>
/// Remove the HTTP and/or HTTPS proxy setting from current machine
/// Remove the HTTP and/or HTTPS proxy setting from current machine
/// </summary>
internal void RemoveProxy(ProxyProtocolType protocolType, bool saveOriginalConfig = true)
{
......@@ -162,7 +163,8 @@ namespace Titanium.Web.Proxy.Helpers
if (existingSystemProxyValues.Count != 0)
{
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
{
......@@ -176,7 +178,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// Removes all types of proxy settings (both http and https)
/// Removes all types of proxy settings (both http and https)
/// </summary>
internal void DisableAllProxy()
{
......@@ -284,7 +286,8 @@ namespace Titanium.Web.Proxy.Helpers
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);
return pi;
......@@ -301,7 +304,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <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>
/// <param name="reg"></param>
private static void PrepareRegistry(RegistryKey reg)
......@@ -318,7 +321,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <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>
private void Refresh()
{
......
......@@ -13,15 +13,15 @@ namespace Titanium.Web.Proxy.Helpers
internal enum IpVersion
{
Ipv4 = 1,
Ipv6 = 2,
Ipv6 = 2
}
internal class TcpHelper
{
/// <summary>
/// Gets the extended TCP table.
/// Gets the extended TCP table.
/// </summary>
/// <returns>Collection of <see cref="TcpRow"/>.</returns>
/// <returns>Collection of <see cref="TcpRow" />.</returns>
internal static TcpTable GetExtendedTcpTable(IpVersion ipVersion)
{
var tcpRows = new List<TcpRow>();
......@@ -37,15 +37,18 @@ namespace Titanium.Web.Proxy.Helpers
try
{
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));
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)));
}
}
......@@ -63,9 +66,9 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// Gets the TCP row by local port number.
/// Gets the TCP row by local port number.
/// </summary>
/// <returns><see cref="TcpRow"/>.</returns>
/// <returns><see cref="TcpRow" />.</returns>
internal static TcpRow GetTcpRowByLocalPort(IpVersion ipVersion, int localPort)
{
var tcpTable = IntPtr.Zero;
......@@ -79,15 +82,18 @@ namespace Titanium.Web.Proxy.Helpers
try
{
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));
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)
{
return new TcpRow(tcpRow);
......@@ -110,9 +116,10 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// relays the input clientStream to the server at the specified host name and port with the given httpCmd and headers as prefix
/// Usefull for websocket requests
/// Asynchronous Programming Model, which does not throw exceptions when the socket is closed
/// relays the input clientStream to the server at the specified host name and port with the given httpCmd and headers
/// as prefix
/// Usefull for websocket requests
/// Asynchronous Programming Model, which does not throw exceptions when the socket is closed
/// </summary>
/// <param name="clientStream"></param>
/// <param name="serverStream"></param>
......@@ -122,18 +129,20 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="exceptionFunc"></param>
/// <returns></returns>
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>();
cancellationTokenSource.Token.Register(() => taskCompletionSource.TrySetResult(true));
//Now async relay all server=>client & client=>server data
byte[] clientBuffer = BufferPool.GetBuffer(bufferSize);
byte[] serverBuffer = BufferPool.GetBuffer(bufferSize);
var clientBuffer = BufferPool.GetBuffer(bufferSize);
var serverBuffer = BufferPool.GetBuffer(bufferSize);
try
{
BeginRead(clientStream, serverStream, clientBuffer, cancellationTokenSource, onDataSend, exceptionFunc);
BeginRead(serverStream, clientStream, serverBuffer, cancellationTokenSource, onDataReceive, exceptionFunc);
BeginRead(serverStream, clientStream, serverBuffer, cancellationTokenSource, onDataReceive,
exceptionFunc);
await taskCompletionSource.Task;
}
finally
......@@ -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)
{
if (cancellationTokenSource.IsCancellationRequested)
......@@ -182,7 +192,8 @@ namespace Titanium.Web.Proxy.Helpers
try
{
outputStream.EndWrite(ar2);
BeginRead(inputStream, outputStream, buffer, cancellationTokenSource, onCopy, exceptionFunc);
BeginRead(inputStream, outputStream, buffer, cancellationTokenSource, onCopy,
exceptionFunc);
}
catch (IOException ex)
{
......@@ -208,9 +219,10 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// relays the input clientStream to the server at the specified host name and port with the given httpCmd and headers as prefix
/// Usefull for websocket requests
/// Task-based Asynchronous Pattern
/// relays the input clientStream to the server at the specified host name and port with the given httpCmd and headers
/// as prefix
/// Usefull for websocket requests
/// Task-based Asynchronous Pattern
/// </summary>
/// <param name="clientStream"></param>
/// <param name="serverStream"></param>
......@@ -220,11 +232,14 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="exceptionFunc"></param>
/// <returns></returns>
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
var sendRelay = clientStream.CopyToAsync(serverStream, onDataSend, bufferSize, cancellationTokenSource.Token);
var receiveRelay = serverStream.CopyToAsync(clientStream, onDataReceive, bufferSize, cancellationTokenSource.Token);
var sendRelay =
clientStream.CopyToAsync(serverStream, onDataSend, bufferSize, cancellationTokenSource.Token);
var receiveRelay =
serverStream.CopyToAsync(clientStream, onDataReceive, bufferSize, cancellationTokenSource.Token);
await Task.WhenAny(sendRelay, receiveRelay);
cancellationTokenSource.Cancel();
......@@ -233,8 +248,9 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// relays the input clientStream to the server at the specified host name and port with the given httpCmd and headers as prefix
/// Usefull for websocket requests
/// relays the input clientStream to the server at the specified host name and port with the given httpCmd and headers
/// as prefix
/// Usefull for websocket requests
/// </summary>
/// <param name="clientStream"></param>
/// <param name="serverStream"></param>
......@@ -244,10 +260,12 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="exceptionFunc"></param>
/// <returns></returns>
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
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;
// Helper classes for setting system proxy settings
namespace Titanium.Web.Proxy.Helpers.WinHttp
{
internal partial class NativeMethods
internal class NativeMethods
{
internal static class WinHttp
{
[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)]
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)]
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)]
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);
[DllImport("winhttp.dll", CharSet = CharSet.Unicode, SetLastError = true)]
......
......@@ -9,11 +9,11 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
{
}
public override bool IsInvalid => handle == IntPtr.Zero;
protected override bool ReleaseHandle()
{
return NativeMethods.WinHttp.WinHttpCloseHandle(handle);
}
public override bool IsInvalid => handle == IntPtr.Zero;
}
}
......@@ -14,6 +14,26 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
private bool autoDetectFailed;
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 ProxyInfo ProxyInfo { get; internal set; }
......@@ -28,27 +48,18 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
private WebProxy proxy { get; set; }
public WinHttpWebProxyFinder()
public void Dispose()
{
session = NativeMethods.WinHttp.WinHttpOpen(null, NativeMethods.WinHttp.AccessType.NoProxy, null, null, 0);
if (session == null || session.IsInvalid)
{
int lastWin32Error = GetLastWin32Error();
}
else
{
int downloadTimeout = 60 * 1000;
if (NativeMethods.WinHttp.WinHttpSetTimeouts(session, downloadTimeout, downloadTimeout, downloadTimeout, downloadTimeout))
return;
int lastWin32Error = GetLastWin32Error();
}
Dispose(true);
}
public bool GetAutoProxies(Uri destination, out IList<string> proxyList)
{
proxyList = null;
if (session == null || session.IsInvalid || state == AutoWebProxyState.UnrecognizedScheme)
{
return false;
}
string proxyListString = null;
var errorCode = NativeMethods.WinHttp.ErrorCodes.AudodetectionFailed;
......@@ -64,11 +75,16 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
}
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);
if (state != AutoWebProxyState.Completed)
{
return false;
}
if (!string.IsNullOrEmpty(proxyListString))
{
......@@ -101,14 +117,16 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
var systemProxy = new ExternalProxy
{
HostName = proxyStr,
Port = port,
Port = port
};
return systemProxy;
}
if (proxy?.IsBypassed(destination) == true)
{
return null;
}
var protocolType = ProxyInfo.ParseProtocolType(destination.Scheme);
if (protocolType.HasValue)
......@@ -119,7 +137,7 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
var systemProxy = new ExternalProxy
{
HostName = value.HostName,
Port = value.Port,
Port = value.Port
};
return systemProxy;
......@@ -159,7 +177,9 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
else
{
if (Marshal.GetLastWin32Error() == 8)
{
throw new OutOfMemoryException();
}
result = new ProxyInfo(true, null, null, null, null);
}
......@@ -180,15 +200,13 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
autoDetectFailed = false;
}
public void Dispose()
{
Dispose(true);
}
private void Dispose(bool disposing)
{
if (!disposing || session == null || session.IsInvalid)
{
return;
}
session.Close();
}
......@@ -201,7 +219,8 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
{
autoProxyOptions.Flags = NativeMethods.WinHttp.AutoProxyFlags.AutoDetect;
autoProxyOptions.AutoConfigUrl = null;
autoProxyOptions.AutoDetectFlags = NativeMethods.WinHttp.AutoDetectType.Dhcp | NativeMethods.WinHttp.AutoDetectType.DnsA;
autoProxyOptions.AutoDetectFlags =
NativeMethods.WinHttp.AutoDetectType.Dhcp | NativeMethods.WinHttp.AutoDetectType.DnsA;
}
else
{
......@@ -218,14 +237,17 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
{
autoProxyOptions.AutoLogonIfChallenged = true;
if (!WinHttpGetProxyForUrl(destination.ToString(), ref autoProxyOptions, out proxyListString))
{
num = GetLastWin32Error();
}
}
}
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;
bool flag;
......@@ -233,15 +255,19 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
RuntimeHelpers.PrepareConstrainedRegions();
try
{
flag = NativeMethods.WinHttp.WinHttpGetProxyForUrl(session, destination, ref autoProxyOptions, out proxyInfo);
flag = NativeMethods.WinHttp.WinHttpGetProxyForUrl(session, destination, ref autoProxyOptions,
out proxyInfo);
if (flag)
{
proxyListString = Marshal.PtrToStringUni(proxyInfo.Proxy);
}
}
finally
{
Marshal.FreeHGlobal(proxyInfo.Proxy);
Marshal.FreeHGlobal(proxyInfo.ProxyBypass);
}
return flag;
}
......@@ -249,7 +275,10 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
{
int lastWin32Error = Marshal.GetLastWin32Error();
if (lastWin32Error == 8)
{
throw new OutOfMemoryException();
}
return lastWin32Error;
}
......@@ -274,7 +303,10 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
private static AutoWebProxyState GetStateFromErrorCode(NativeMethods.WinHttp.ErrorCodes errorCode)
{
if (errorCode == 0L)
{
return AutoWebProxyState.Completed;
}
switch (errorCode)
{
case NativeMethods.WinHttp.ErrorCodes.UnableToDownloadScript:
......@@ -298,7 +330,9 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
foreach (char c in value)
{
if (!char.IsWhiteSpace(c))
{
stringBuilder.Append(c);
}
}
return stringBuilder.ToString();
......@@ -325,7 +359,7 @@ namespace Titanium.Web.Proxy.Helpers.WinHttp
DownloadFailure,
CompilationFailure,
UnrecognizedScheme,
Completed,
Completed
}
}
}
......@@ -4,11 +4,11 @@ namespace Titanium.Web.Proxy.Http
{
public class ConnectRequest : Request
{
public ClientHelloInfo ClientHelloInfo { get; set; }
public ConnectRequest()
{
Method = "CONNECT";
}
public ClientHelloInfo ClientHelloInfo { get; set; }
}
}
using StreamExtended;
using System;
using System;
using System.Net;
using StreamExtended;
namespace Titanium.Web.Proxy.Http
{
......@@ -9,7 +9,7 @@ namespace Titanium.Web.Proxy.Http
public ServerHelloInfo ServerHelloInfo { get; set; }
/// <summary>
/// Creates a successfull CONNECT response
/// Creates a successfull CONNECT response
/// </summary>
/// <param name="httpVersion"></param>
/// <returns></returns>
......
......@@ -16,28 +16,44 @@ namespace Titanium.Web.Proxy.Http
private readonly Dictionary<string, List<HttpHeader>> nonUniqueHeaders;
/// <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>
public ReadOnlyDictionary<string, HttpHeader> Headers { get; }
/// <summary>
/// Non Unique headers
/// Non Unique headers
/// </summary>
public ReadOnlyDictionary<string, List<HttpHeader>> NonUniqueHeaders { get; }
/// <summary>
/// Initializes a new instance of the <see cref="HeaderCollection"/> class.
/// Returns an enumerator that iterates through the collection.
/// </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);
nonUniqueHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase);
Headers = new ReadOnlyDictionary<string, HttpHeader>(headers);
NonUniqueHeaders = new ReadOnlyDictionary<string, List<HttpHeader>>(nonUniqueHeaders);
return headers.Values.Concat(nonUniqueHeaders.Values.SelectMany(x => x)).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// True if header exists
/// True if header exists
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
......@@ -47,8 +63,8 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// Returns all headers with given name if exists
/// Returns null if does'nt exist
/// Returns all headers with given name if exists
/// Returns null if does'nt exist
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
......@@ -86,7 +102,7 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// Returns all headers
/// Returns all headers
/// </summary>
/// <returns></returns>
public List<HttpHeader> GetAllHeaders()
......@@ -100,7 +116,7 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// Add a new header with given name and value
/// Add a new header with given name and value
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
......@@ -110,7 +126,7 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// Adds the given header object to Request
/// Adds the given header object to Request
/// </summary>
/// <param name="newHeader"></param>
public void AddHeader(HttpHeader newHeader)
......@@ -142,7 +158,7 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// Adds the given header objects to Request
/// Adds the given header objects to Request
/// </summary>
/// <param name="newHeaders"></param>
public void AddHeaders(IEnumerable<HttpHeader> newHeaders)
......@@ -159,7 +175,7 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// Adds the given header objects to Request
/// Adds the given header objects to Request
/// </summary>
/// <param name="newHeaders"></param>
public void AddHeaders(IEnumerable<KeyValuePair<string, string>> newHeaders)
......@@ -176,7 +192,7 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// Adds the given header objects to Request
/// Adds the given header objects to Request
/// </summary>
/// <param name="newHeaders"></param>
public void AddHeaders(IEnumerable<KeyValuePair<string, HttpHeader>> newHeaders)
......@@ -190,7 +206,8 @@ namespace Titanium.Web.Proxy.Http
{
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);
......@@ -198,11 +215,13 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// removes all headers with given name
/// removes all headers with given name
/// </summary>
/// <param name="headerName"></param>
/// <returns>True if header was removed
/// False if no header exists with given name</returns>
/// <returns>
/// True if header was removed
/// False if no header exists with given name
/// </returns>
public bool RemoveHeader(string headerName)
{
bool result = headers.Remove(headerName);
......@@ -217,7 +236,7 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// Removes given header object if it exist
/// Removes given header object if it exist
/// </summary>
/// <param name="header">Returns true if header exists and was removed </param>
public bool RemoveHeader(HttpHeader header)
......@@ -242,7 +261,7 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// Removes all the headers.
/// Removes all the headers.
/// </summary>
public void Clear()
{
......@@ -273,7 +292,7 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// Fix proxy specific headers
/// Fix proxy specific headers
/// </summary>
internal void FixProxyHeaders()
{
......@@ -286,21 +305,5 @@ namespace Titanium.Web.Proxy.Http
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.Collections.Generic;
using System.Threading.Tasks;
using StreamExtended.Network;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Http
......@@ -20,7 +18,7 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// Increase size of buffer and copy existing content to new buffer
/// Increase size of buffer and copy existing content to new buffer
/// </summary>
/// <param name="buffer"></param>
/// <param name="size"></param>
......
......@@ -9,66 +9,66 @@ using Titanium.Web.Proxy.Network.Tcp;
namespace Titanium.Web.Proxy.Http
{
/// <summary>
/// Used to communicate with the server over HTTP(S)
/// Used to communicate with the server over HTTP(S)
/// </summary>
public class HttpWebClient
{
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>
/// Connection to server
/// Connection to server
/// </summary>
internal TcpConnection ServerConnection { get; set; }
/// <summary>
/// Request ID.
/// Request ID.
/// </summary>
public Guid RequestId { get; }
/// <summary>
/// Override UpStreamEndPoint for this request; Local NIC via request is made
/// Override UpStreamEndPoint for this request; Local NIC via request is made
/// </summary>
public IPEndPoint UpStreamEndPoint { get; set; }
/// <summary>
/// Headers passed with Connect.
/// Headers passed with Connect.
/// </summary>
public ConnectRequest ConnectRequest { get; internal set; }
/// <summary>
/// Web Request.
/// Web Request.
/// </summary>
public Request Request { get; }
/// <summary>
/// Web Response.
/// Web Response.
/// </summary>
public Response Response { get; internal set; }
/// <summary>
/// PID of the process that is created the current session when client is running in this machine
/// If client is remote then this will return
/// PID of the process that is created the current session when client is running in this machine
/// If client is remote then this will return
/// </summary>
public Lazy<int> ProcessId { get; internal set; }
/// <summary>
/// Is Https?
/// Is Https?
/// </summary>
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>
/// Set the tcp connection to server used by this webclient
/// Set the tcp connection to server used by this webclient
/// </summary>
/// <param name="connection">Instance of <see cref="TcpConnection"/></param>
/// <param name="connection">Instance of <see cref="TcpConnection" /></param>
internal void SetConnection(TcpConnection connection)
{
connection.LastAccess = DateTime.Now;
......@@ -76,7 +76,7 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// Prepare and send the http(s) request
/// Prepare and send the http(s) request
/// </summary>
/// <returns></returns>
internal async Task SendRequest(bool enable100ContinueBehaviour, bool isTransparent)
......@@ -95,12 +95,13 @@ namespace Titanium.Web.Proxy.Http
//Send Authentication to Upstream proxy if needed
if (!isTransparent && upstreamProxy != null
&& ServerConnection.IsHttps == false
&& !string.IsNullOrEmpty(upstreamProxy.UserName)
&& upstreamProxy.Password != null)
&& ServerConnection.IsHttps == false
&& !string.IsNullOrEmpty(upstreamProxy.UserName)
&& upstreamProxy.Password != null)
{
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
......@@ -120,7 +121,8 @@ namespace Titanium.Web.Proxy.Http
{
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
if (responseStatusCode == (int)HttpStatusCode.Continue
......@@ -140,14 +142,16 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// Receive and parse the http response from server
/// Receive and parse the http response from server
/// </summary>
/// <returns></returns>
internal async Task ReceiveResponse()
{
//return if this is already read
if (Response.StatusCode != 0)
{
return;
}
string httpStatus = await ServerConnection.StreamReader.ReadLineAsync();
if (httpStatus == null)
......@@ -157,7 +161,6 @@ namespace Titanium.Web.Proxy.Http
if (httpStatus == string.Empty)
{
//Empty content in first-line, try again
httpStatus = await ServerConnection.StreamReader.ReadLineAsync();
}
......@@ -199,7 +202,7 @@ namespace Titanium.Web.Proxy.Http
}
/// <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>
internal void FinishSession()
{
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Http
namespace Titanium.Web.Proxy.Http
{
public static class KnownHeaders
{
......
......@@ -9,33 +9,33 @@ using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Http
{
/// <summary>
/// Http(s) request object
/// Http(s) request object
/// </summary>
[TypeConverter(typeof(ExpandableObjectConverter))]
public class Request : RequestResponseBase
{
/// <summary>
/// Request Method
/// Request Method
/// </summary>
public string Method { get; set; }
/// <summary>
/// Request HTTP Uri
/// Request HTTP Uri
/// </summary>
public Uri RequestUri { get; set; }
/// <summary>
/// Is Https?
/// Is Https?
/// </summary>
public bool IsHttps => RequestUri.Scheme == ProxyServer.UriSchemeHttps;
/// <summary>
/// The original request Url.
/// The original request Url.
/// </summary>
public string OriginalUrl { get; set; }
/// <summary>
/// Has request body?
/// Has request body?
/// </summary>
public override bool HasBody
{
......@@ -66,9 +66,9 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// Http hostname header value if exists
/// Note: Changing this does NOT change host in RequestUri
/// Users can set new RequestUri separately
/// Http hostname header value if exists
/// Note: Changing this does NOT change host in RequestUri
/// Users can set new RequestUri separately
/// </summary>
public string Host
{
......@@ -77,7 +77,7 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// Does this request has a 100-continue header?
/// Does this request has a 100-continue header?
/// </summary>
public bool ExpectContinue
{
......@@ -91,47 +91,17 @@ namespace Titanium.Web.Proxy.Http
public bool IsMultipartFormData => ContentType?.StartsWith("multipart/form-data") == true;
/// <summary>
/// Request Url
/// Request Url
/// </summary>
public string Url => RequestUri.OriginalString;
/// <summary>
/// Terminates the underlying Tcp Connection to client after current request
/// Terminates the underlying Tcp Connection to client after current request
/// </summary>
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>
/// Does this request has an upgrade to websocket header?
/// Does this request has an upgrade to websocket header?
/// </summary>
public bool UpgradeToWebSocket
{
......@@ -149,17 +119,17 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// Does server responsed positively for 100 continue request
/// Does server responsed positively for 100 continue request
/// </summary>
public bool Is100Continue { get; internal set; }
/// <summary>
/// Server responsed negatively for the request for 100 continue
/// Server responsed negatively for the request for 100 continue
/// </summary>
public bool ExpectationFailed { get; internal set; }
/// <summary>
/// Gets the header text.
/// Gets the header text.
/// </summary>
public override string HeaderText
{
......@@ -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)
{
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)
var httpCmdSplit = httpCmd.Split(ProxyConstants.SpaceSplit, 3);
......@@ -196,11 +198,6 @@ namespace Titanium.Web.Proxy.Http
httpMethod = httpCmdSplit[0];
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();
}
......
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using Titanium.Web.Proxy.Compression;
using Titanium.Web.Proxy.Extensions;
......@@ -14,32 +12,37 @@ namespace Titanium.Web.Proxy.Http
public abstract class RequestResponseBase
{
/// <summary>
/// Cached body content as byte array
/// Cached body content as byte array
/// </summary>
protected byte[] BodyInternal;
/// <summary>
/// Cached body as string
/// Cached body as string
/// </summary>
private string bodyString;
/// <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>
public bool KeepBody { get; set; }
/// <summary>
/// Http Version
/// Http Version
/// </summary>
public Version HttpVersion { get; set; } = HttpHeader.VersionUnknown;
/// <summary>
/// Collection of all headers
/// Collection of all headers
/// </summary>
public HeaderCollection Headers { get; } = new HeaderCollection();
/// <summary>
/// Length of the body
/// Length of the body
/// </summary>
public long ContentLength
{
......@@ -74,17 +77,17 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// Content encoding for this request/response
/// Content encoding for this request/response
/// </summary>
public string ContentEncoding => Headers.GetHeaderValueOrNull(KnownHeaders.ContentEncoding)?.Trim();
/// <summary>
/// Encoding for this request/response
/// Encoding for this request/response
/// </summary>
public Encoding Encoding => HttpHelper.GetEncodingFromContentType(ContentType);
/// <summary>
/// Content-type of the request/response
/// Content-type of the request/response
/// </summary>
public string ContentType
{
......@@ -93,7 +96,7 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// Is body send as chunked bytes
/// Is body send as chunked bytes
/// </summary>
public bool IsChunked
{
......@@ -119,7 +122,7 @@ namespace Titanium.Web.Proxy.Http
public abstract string HeaderText { get; }
/// <summary>
/// Body as byte array
/// Body as byte array
/// </summary>
[Browsable(false)]
public byte[] Body
......@@ -140,19 +143,31 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// Has the request/response body?
/// Has the request/response body?
/// </summary>
public abstract bool HasBody { get; }
/// <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>
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);
/// <summary>
/// get the compressed body from given bytes
/// get the compressed body from given bytes
/// </summary>
/// <param name="encodingType"></param>
/// <param name="body"></param>
......@@ -205,30 +220,13 @@ namespace Titanium.Web.Proxy.Http
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()
{
ContentLength = IsChunked ? -1 : BodyInternal?.Length ?? 0;
}
/// <summary>
/// Finish the session
/// Finish the session
/// </summary>
internal void FinishSession()
{
......
......@@ -8,23 +8,38 @@ using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Http
{
/// <summary>
/// Http(s) response object
/// Http(s) response object
/// </summary>
[TypeConverter(typeof(ExpandableObjectConverter))]
public class Response : RequestResponseBase
{
/// <summary>
/// Response Status Code.
/// Constructor.
/// </summary>
public Response()
{
}
/// <summary>
/// Constructor.
/// </summary>
public Response(byte[] body)
{
Body = body;
}
/// <summary>
/// Response Status Code.
/// </summary>
public int StatusCode { get; set; }
/// <summary>
/// Response Status description.
/// Response Status description.
/// </summary>
public string StatusDescription { get; set; }
/// <summary>
/// Has response body?
/// Has response body?
/// </summary>
public override bool HasBody
{
......@@ -57,7 +72,7 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// Keep the connection alive?
/// Keep the connection alive?
/// </summary>
public bool KeepAlive
{
......@@ -79,38 +94,23 @@ namespace Titanium.Web.Proxy.Http
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>
/// Is response 100-continue
/// Is response 100-continue
/// </summary>
public bool Is100Continue { get; internal set; }
/// <summary>
/// expectation failed returned by server?
/// expectation failed returned by server?
/// </summary>
public bool ExpectationFailed { get; internal set; }
/// <summary>
/// Gets the resposne status.
/// Gets the resposne status.
/// </summary>
public string Status => CreateResponseLine(HttpVersion, StatusCode, StatusDescription);
/// <summary>
/// Gets the header text.
/// Gets the header text.
/// </summary>
public override string HeaderText
{
......@@ -128,19 +128,19 @@ namespace Titanium.Web.Proxy.Http
}
}
/// <summary>
/// Constructor.
/// </summary>
public Response()
internal override void EnsureBodyAvailable(bool throwWhenNotReadYet = true)
{
}
if (BodyInternal != null)
{
return;
}
/// <summary>
/// Constructor.
/// </summary>
public Response(byte[] body)
{
Body = body;
if (!IsBodyRead && throwWhenNotReadYet)
{
throw new Exception("Response body is not read yet. " +
"Use SessionEventArgs.GetResponseBody() or SessionEventArgs.GetResponseBodyAsString() " +
"method to read the response body.");
}
}
internal static string CreateResponseLine(Version version, int statusCode, string statusDescription)
......@@ -148,7 +148,8 @@ namespace Titanium.Web.Proxy.Http
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);
if (httpResult.Length != 3)
......
......@@ -3,12 +3,12 @@
namespace Titanium.Web.Proxy.Http.Responses
{
/// <summary>
/// 200 Ok response
/// 200 Ok response
/// </summary>
public sealed class OkResponse : Response
{
/// <summary>
/// Constructor.
/// Constructor.
/// </summary>
public OkResponse()
{
......@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy.Http.Responses
}
/// <summary>
/// Constructor.
/// Constructor.
/// </summary>
public OkResponse(byte[] body) : this()
{
......
......@@ -3,12 +3,12 @@
namespace Titanium.Web.Proxy.Http.Responses
{
/// <summary>
/// Redirect response
/// Redirect response
/// </summary>
public sealed class RedirectResponse : Response
{
/// <summary>
/// Constructor.
/// Constructor.
/// </summary>
public RedirectResponse()
{
......
......@@ -7,44 +7,47 @@ using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy.Models
{
/// <summary>
/// A proxy endpoint that the client is aware of
/// So client application know that it is communicating with a proxy server
/// A proxy endpoint that the client is aware of
/// So client application know that it is communicating with a proxy server
/// </summary>
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 IsSystemHttpsProxy { get; set; }
/// <summary>
/// Generic certificate to use for SSL decryption.
/// Generic certificate to use for SSL decryption.
/// </summary>
public X509Certificate2 GenericCertificate { get; set; }
/// <summary>
/// Intercept tunnel connect request
/// 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
/// Intercept tunnel connect request
/// 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
/// </summary>
public event AsyncEventHandler<TunnelConnectSessionEventArgs> BeforeTunnelConnectRequest;
/// <summary>
/// Intercept tunnel connect response
/// Valid only for explicit endpoints
/// Intercept tunnel connect response
/// Valid only for explicit endpoints
/// </summary>
public event AsyncEventHandler<TunnelConnectSessionEventArgs> BeforeTunnelConnectResponse;
/// <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 async Task InvokeBeforeTunnelConnectRequest(ProxyServer proxyServer, TunnelConnectSessionEventArgs connectArgs, ExceptionHandler exceptionFunc)
internal async Task InvokeBeforeTunnelConnectRequest(ProxyServer proxyServer,
TunnelConnectSessionEventArgs connectArgs, ExceptionHandler exceptionFunc)
{
if (BeforeTunnelConnectRequest != null)
{
......@@ -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)
{
......@@ -61,4 +65,4 @@ namespace Titanium.Web.Proxy.Models
}
}
}
}
\ No newline at end of file
}
......@@ -4,27 +4,29 @@ using System.Net;
namespace Titanium.Web.Proxy.Models
{
/// <summary>
/// An upstream proxy this proxy uses if any
/// An upstream proxy this proxy uses if any
/// </summary>
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 userName;
/// <summary>
/// Use default windows credentials?
/// Use default windows credentials?
/// </summary>
public bool UseDefaultCredentials { get; set; }
/// <summary>
/// Bypass this proxy for connections to localhost?
/// Bypass this proxy for connections to localhost?
/// </summary>
public bool BypassLocalhost { get; set; }
/// <summary>
/// Username.
/// Username.
/// </summary>
public string UserName
{
......@@ -41,7 +43,7 @@ namespace Titanium.Web.Proxy.Models
}
/// <summary>
/// Password.
/// Password.
/// </summary>
public string Password
{
......@@ -58,12 +60,12 @@ namespace Titanium.Web.Proxy.Models
}
/// <summary>
/// Host name.
/// Host name.
/// </summary>
public string HostName { get; set; }
/// <summary>
/// Port.
/// Port.
/// </summary>
public int Port { get; set; }
......
......@@ -7,7 +7,7 @@ using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Models
{
/// <summary>
/// Http Header object used by proxy
/// Http Header object used by proxy
/// </summary>
public class HttpHeader
{
......@@ -20,7 +20,7 @@ namespace Titanium.Web.Proxy.Models
internal static HttpHeader ProxyConnectionKeepAlive = new HttpHeader("Proxy-Connection", "keep-alive");
/// <summary>
/// Constructor.
/// Constructor.
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
......@@ -37,17 +37,17 @@ namespace Titanium.Web.Proxy.Models
}
/// <summary>
/// Header Name.
/// Header Name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Header Value.
/// Header Value.
/// </summary>
public string Value { get; set; }
/// <summary>
/// Returns header as a valid header string
/// Returns header as a valid header string
/// </summary>
/// <returns></returns>
public override string ToString()
......
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net;
using System.Net.Sockets;
using System.Text.RegularExpressions;
namespace Titanium.Web.Proxy.Models
{
/// <summary>
/// An abstract endpoint where the proxy listens
/// An abstract endpoint where the proxy listens
/// </summary>
public abstract class ProxyEndPoint
{
/// <summary>
/// Constructor.
/// Constructor.
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
......@@ -25,27 +22,27 @@ namespace Titanium.Web.Proxy.Models
}
/// <summary>
/// underlying TCP Listener object
/// underlying TCP Listener object
/// </summary>
internal TcpListener Listener { get; set; }
/// <summary>
/// Ip Address we are listening.
/// Ip Address we are listening.
/// </summary>
public IPAddress IpAddress { get; }
/// <summary>
/// Port we are listening.
/// Port we are listening.
/// </summary>
public int Port { get; internal set; }
/// <summary>
/// Enable SSL?
/// Enable SSL?
/// </summary>
public bool DecryptSsl { get; }
/// <summary>
/// Is IPv6 enabled?
/// Is IPv6 enabled?
/// </summary>
public bool IpV6Enabled => Equals(IpAddress, IPAddress.IPv6Any)
|| Equals(IpAddress, IPAddress.IPv6Loopback)
......
......@@ -6,34 +6,36 @@ using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy.Models
{
/// <summary>
/// A proxy end point client is not aware of
/// Usefull when requests are redirected to this proxy end point through port forwarding
/// A proxy end point client is not aware of
/// Usefull when requests are redirected to this proxy end point through port forwarding
/// </summary>
public class TransparentProxyEndPoint : ProxyEndPoint
{
/// <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;
/// <summary>
/// Constructor.
/// Constructor.
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <param name="decryptSsl"></param>
public TransparentProxyEndPoint(IPAddress ipAddress, int port, bool decryptSsl = true) : base(ipAddress, port, decryptSsl)
public TransparentProxyEndPoint(IPAddress ipAddress, int port, bool decryptSsl = true) : base(ipAddress, port,
decryptSsl)
{
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)
{
......@@ -41,4 +43,4 @@ namespace Titanium.Web.Proxy.Models
}
}
}
}
\ No newline at end of file
}
......@@ -4,21 +4,21 @@ using System.Security.Cryptography.X509Certificates;
namespace Titanium.Web.Proxy.Network
{
/// <summary>
/// An object that holds the cached certificate
/// An object that holds the cached certificate
/// </summary>
internal class CachedCertificate
{
internal CachedCertificate()
{
LastAccess = DateTime.Now;
}
internal X509Certificate2 Certificate { get; set; }
/// <summary>
/// last time this certificate was used
/// Usefull in determining its cache lifetime
/// last time this certificate was used
/// Usefull in determining its cache lifetime
/// </summary>
internal DateTime LastAccess { get; set; }
internal CachedCertificate()
{
LastAccess = DateTime.Now;
}
}
}
......@@ -17,11 +17,12 @@ using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.X509;
using Titanium.Web.Proxy.Shared;
using X509Certificate = Org.BouncyCastle.X509.X509Certificate;
namespace Titanium.Web.Proxy.Network.Certificate
{
/// <summary>
/// Implements certificate generation operations.
/// Implements certificate generation operations.
/// </summary>
internal class BCCertificateMaker : ICertificateMaker
{
......@@ -40,7 +41,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
}
/// <summary>
/// Makes the certificate.
/// Makes the certificate.
/// </summary>
/// <param name="sSubjectCn">The s subject cn.</param>
/// <param name="isRoot">if set to <c>true</c> [is root].</param>
......@@ -52,7 +53,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
}
/// <summary>
/// Generates the certificate.
/// Generates the certificate.
/// </summary>
/// <param name="subjectName">Name of the subject.</param>
/// <param name="issuerName">Name of the issuer.</param>
......@@ -79,7 +80,8 @@ namespace Titanium.Web.Proxy.Network.Certificate
var certificateGenerator = new X509V3CertificateGenerator();
// 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);
// Issuer and Subject Name
......@@ -94,12 +96,13 @@ namespace Titanium.Web.Proxy.Network.Certificate
if (hostName != null)
{
//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);
certificateGenerator.AddExtension(X509Extensions.SubjectAlternativeName.Id, false, subjectAlternativeNamesExtension);
certificateGenerator.AddExtension(X509Extensions.SubjectAlternativeName.Id, false,
subjectAlternativeNamesExtension);
}
// Subject Public Key
var keyGenerationParameters = new KeyGenerationParameters(secureRandom, keyStrength);
var keyPairGenerator = new RsaKeyPairGenerator();
......@@ -109,13 +112,15 @@ namespace Titanium.Web.Proxy.Network.Certificate
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
// 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)
{
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
var certificate = certificateGenerator.Generate(signatureFactory);
......@@ -131,7 +136,8 @@ namespace Titanium.Web.Proxy.Network.Certificate
}
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);
#if NET45
......@@ -158,7 +164,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
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";
var store = new Pkcs12Store();
......@@ -173,9 +179,9 @@ namespace Titanium.Web.Proxy.Network.Certificate
return new X509Certificate2(ms.ToArray(), password, X509KeyStorageFlags.Exportable);
}
}
/// <summary>
/// Makes the certificate internal.
/// Makes the certificate internal.
/// </summary>
/// <param name="isRoot">if set to <c>true</c> [is root].</param>
/// <param name="hostName">hostname for certificate</param>
......@@ -184,29 +190,33 @@ namespace Titanium.Web.Proxy.Network.Certificate
/// <param name="validTo">The valid to.</param>
/// <param name="signingCertificate">The signing certificate.</param>
/// <returns>X509Certificate2 instance.</returns>
/// <exception cref="System.ArgumentException">You must specify a Signing Certificate if and only if you are not creating a root.</exception>
/// <exception cref="System.ArgumentException">
/// You must specify a Signing Certificate if and only if you are not creating a
/// root.
/// </exception>
private X509Certificate2 MakeCertificateInternal(bool isRoot,
string hostName, string subjectName,
DateTime validFrom, DateTime validTo, X509Certificate2 signingCertificate)
{
if (isRoot != (null == signingCertificate))
{
throw new ArgumentException("You must specify a Signing Certificate if and only if you are not creating a root.", nameof(signingCertificate));
throw new ArgumentException(
"You must specify a Signing Certificate if and only if you are not creating a root.",
nameof(signingCertificate));
}
if (isRoot)
{
return GenerateCertificate(null, subjectName, subjectName, validFrom, validTo);
}
else
{
var kp = DotNetUtilities.GetKeyPair(signingCertificate.PrivateKey);
return GenerateCertificate(hostName, subjectName, signingCertificate.Subject, validFrom, validTo, issuerPrivateKey: kp.Private);
}
var kp = DotNetUtilities.GetKeyPair(signingCertificate.PrivateKey);
return GenerateCertificate(hostName, subjectName, signingCertificate.Subject, validFrom, validTo,
issuerPrivateKey: kp.Private);
}
/// <summary>
/// Makes the certificate internal.
/// Makes the certificate internal.
/// </summary>
/// <param name="subject">The s subject cn.</param>
/// <param name="isRoot">if set to <c>true</c> [is root].</param>
......@@ -216,7 +226,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
/// <returns>X509Certificate2.</returns>
private X509Certificate2 MakeCertificateInternal(string subject, bool isRoot,
bool switchToMtaIfNeeded, X509Certificate2 signingCert = null,
CancellationToken cancellationToken = default(CancellationToken))
CancellationToken cancellationToken = default)
{
#if NET45
if (switchToMtaIfNeeded && Thread.CurrentThread.GetApartmentState() != ApartmentState.MTA)
......@@ -248,7 +258,9 @@ namespace Titanium.Web.Proxy.Network.Certificate
}
#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 @@
namespace Titanium.Web.Proxy.Network.Certificate
{
/// <summary>
/// Abstract interface for different Certificate Maker Engines
/// Abstract interface for different Certificate Maker Engines
/// </summary>
internal interface ICertificateMaker
{
......
......@@ -6,47 +6,46 @@ using System.Threading;
namespace Titanium.Web.Proxy.Network.Certificate
{
/// <summary>
/// Certificate Maker - uses MakeCert
/// Calls COM objects using reflection
/// Certificate Maker - uses MakeCert
/// Calls COM objects using reflection
/// </summary>
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 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 readonly ExceptionHandler exceptionFunc;
/// <summary>
/// Constructor.
/// Constructor.
/// </summary>
internal WinCertificateMaker(ExceptionHandler exceptionFunc)
{
......@@ -71,7 +70,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
/// <summary>
/// Make certificate.
/// Make certificate.
/// </summary>
/// <param name="sSubjectCN"></param>
/// <param name="isRoot"></param>
......@@ -88,7 +87,9 @@ namespace Titanium.Web.Proxy.Network.Certificate
{
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);
......@@ -114,20 +115,25 @@ namespace Titanium.Web.Proxy.Network.Certificate
{
sharedPrivateKey = Activator.CreateInstance(typeX509PrivateKey);
typeValue = new object[] { sProviderName };
typeX509PrivateKey.InvokeMember("ProviderName", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue);
typeX509PrivateKey.InvokeMember("ProviderName", BindingFlags.PutDispProperty, null, sharedPrivateKey,
typeValue);
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 };
typeX509PrivateKey.InvokeMember("KeySpec", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue);
typeX509PrivateKey.InvokeMember("KeySpec", BindingFlags.PutDispProperty, null, sharedPrivateKey,
typeValue);
if (!isRoot)
{
typeValue = new object[] { 176 };
typeX509PrivateKey.InvokeMember("KeyUsage", BindingFlags.PutDispProperty, null, sharedPrivateKey, typeValue);
typeX509PrivateKey.InvokeMember("KeyUsage", BindingFlags.PutDispProperty, null, sharedPrivateKey,
typeValue);
}
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);
if (!isRoot)
......@@ -153,7 +159,8 @@ namespace Titanium.Web.Proxy.Network.Certificate
var requestCert = Activator.CreateInstance(typeRequestCert);
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 };
typeRequestCert.InvokeMember("Subject", BindingFlags.PutDispProperty, null, requestCert, typeValue);
typeValue[0] = x500RootCertDN;
......@@ -168,7 +175,8 @@ namespace Titanium.Web.Proxy.Network.Certificate
typeValue[0] = 176;
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];
if (!isRoot)
......@@ -190,10 +198,12 @@ namespace Titanium.Web.Proxy.Network.Certificate
var altDnsNames = Activator.CreateInstance(typeCAlternativeName);
typeValue = new object[] { 3, subject };
typeCAlternativeName.InvokeMember("InitializeFromString", BindingFlags.InvokeMethod, null, altDnsNames, typeValue);
typeCAlternativeName.InvokeMember("InitializeFromString", BindingFlags.InvokeMethod, null, altDnsNames,
typeValue);
typeValue = new[] { altDnsNames };
typeAltNamesCollection.InvokeMember("Add", BindingFlags.InvokeMethod, null, altNameCollection, typeValue);
typeAltNamesCollection.InvokeMember("Add", BindingFlags.InvokeMethod, null, altNameCollection,
typeValue);
typeValue = new[] { altNameCollection };
......@@ -208,16 +218,19 @@ namespace Titanium.Web.Proxy.Network.Certificate
var signerCertificate = Activator.CreateInstance(typeSignerCertificate);
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 };
typeRequestCert.InvokeMember("SignerCertificate", BindingFlags.PutDispProperty, null, requestCert, typeValue);
typeRequestCert.InvokeMember("SignerCertificate", BindingFlags.PutDispProperty, null, requestCert,
typeValue);
}
else
{
var basicConstraints = Activator.CreateInstance(typeBasicConstraints);
typeValue = new object[] { "true", "0" };
typeBasicConstraints.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, basicConstraints, typeValue);
typeBasicConstraints.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, basicConstraints,
typeValue);
typeValue = new[] { basicConstraints };
typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, certificate, typeValue);
}
......@@ -234,29 +247,34 @@ namespace Titanium.Web.Proxy.Network.Certificate
var x509Enrollment = Activator.CreateInstance(typeX509Enrollment);
typeValue[0] = requestCert;
typeX509Enrollment.InvokeMember("InitializeFromRequest", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue);
typeX509Enrollment.InvokeMember("InitializeFromRequest", BindingFlags.InvokeMethod, null, x509Enrollment,
typeValue);
if (isRoot)
{
typeValue[0] = fullSubject;
typeX509Enrollment.InvokeMember("CertificateFriendlyName", BindingFlags.PutDispProperty, null, x509Enrollment, typeValue);
typeX509Enrollment.InvokeMember("CertificateFriendlyName", BindingFlags.PutDispProperty, null,
x509Enrollment, typeValue);
}
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 };
typeX509Enrollment.InvokeMember("InstallResponse", BindingFlags.InvokeMethod, null, x509Enrollment, typeValue);
typeX509Enrollment.InvokeMember("InstallResponse", BindingFlags.InvokeMethod, null, x509Enrollment,
typeValue);
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);
}
private X509Certificate2 MakeCertificateInternal(string sSubjectCN, bool isRoot,
bool switchToMTAIfNeeded, X509Certificate2 signingCert = null,
CancellationToken cancellationToken = default(CancellationToken))
CancellationToken cancellationToken = default)
{
X509Certificate2 certificate = null;
if (switchToMTAIfNeeded && Thread.CurrentThread.GetApartmentState() != ApartmentState.MTA)
......@@ -299,7 +317,8 @@ namespace Titanium.Web.Proxy.Network.Certificate
var graceTime = DateTime.Now.AddDays(GraceDays);
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;
}
}
......
......@@ -14,79 +14,129 @@ using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Network
{
/// <summary>
/// Certificate Engine option
/// Certificate Engine option
/// </summary>
public enum CertificateEngine
{
/// <summary>
/// Uses Windows Certification Generation API
/// Uses Windows Certification Generation API
/// </summary>
DefaultWindows = 0,
/// <summary>
/// Uses BouncyCastle 3rd party library
/// Uses BouncyCastle 3rd party library
/// </summary>
BouncyCastle = 1
}
/// <summary>
/// A class to manage SSL certificates used by this proxy server
/// A class to manage SSL certificates used by this proxy server
/// </summary>
public sealed class CertificateManager : IDisposable
{
private const string defaultRootCertificateIssuer = "Titanium";
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 CertificateEngine engine;
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>
/// Cache dictionary
/// Constructor.
/// </summary>
private readonly ConcurrentDictionary<string, CachedCertificate> certificateCache;
private readonly ConcurrentDictionary<string, Task<X509Certificate2>> pendingCertificateCreationTasks;
/// <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>
/// <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>
/// Is the root certificate used by this proxy is valid?
/// Is the root certificate used by this proxy is valid?
/// </summary>
internal bool CertValidated => RootCertificate != null;
/// <summary>
/// Trust the RootCertificate used by this proxy server for current user
/// Trust the RootCertificate used by this proxy server for current user
/// </summary>
internal bool UserTrustRoot { get; set; } = false;
internal bool UserTrustRoot { get; set; }
/// <summary>
/// Trust the RootCertificate used by this proxy server for current machine
/// Needs elevated permission, otherwise will fail silently.
/// Trust the RootCertificate used by this proxy server for current machine
/// Needs elevated permission, otherwise will fail silently.
/// </summary>
internal bool MachineTrustRoot { get; set; } = false;
internal bool MachineTrustRoot { get; set; }
/// <summary>
/// Whether trust operations should be done with elevated privillages
/// Will prompt with UAC if required. Works only on Windows.
/// Whether trust operations should be done with elevated privillages
/// Will prompt with UAC if required. Works only on Windows.
/// </summary>
internal bool TrustRootAsAdministrator { get; set; } = false;
internal bool TrustRootAsAdministrator { get; set; }
/// <summary>
/// Select Certificate Engine
/// Optionally set to BouncyCastle
/// Mono only support BouncyCastle and it is the default
/// Select Certificate Engine
/// Optionally set to BouncyCastle
/// Mono only support BouncyCastle and it is the default
/// </summary>
public CertificateEngine CertificateEngine
{
......@@ -115,48 +165,45 @@ namespace Titanium.Web.Proxy.Network
}
/// <summary>
/// Password of the Root certificate file
/// <para>Set a password for the .pfx file</para>
/// Password of the Root certificate file
/// <para>Set a password for the .pfx file</para>
/// </summary>
public string PfxPassword { get; set; } = string.Empty;
/// <summary>
/// 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>
/// 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>
/// </summary>
public string PfxFilePath { get; set; } = string.Empty;
/// <summary>
/// Name of the root certificate issuer
/// (This is valid only when RootCertificate property is not set)
/// Name of the root certificate issuer
/// (This is valid only when RootCertificate property is not set)
/// </summary>
public string RootCertificateIssuerName
{
get => issuer ?? defaultRootCertificateIssuer;
set
{
issuer = value;
}
set => issuer = value;
}
/// <summary>
/// Name of the root certificate
/// (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
/// The provided root certificate will be stored in proxy exe directory with the private key
/// Root certificate file will be named as "rootCert.pfx"
/// Name of the root certificate
/// (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
/// The provided root certificate will be stored in proxy exe directory with the private key
/// Root certificate file will be named as "rootCert.pfx"
/// </summary>
public string RootCertificateName
{
get => rootCertificateName ?? defaultRootRootCertificateName;
set
{
rootCertificateName = value;
}
set => rootCertificateName = value;
}
/// <summary>
/// The root certificate
/// The root certificate
/// </summary>
public X509Certificate2 RootCertificate
{
......@@ -169,72 +216,32 @@ namespace Titanium.Web.Proxy.Network
}
/// <summary>
/// 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>
/// 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>
/// </summary>
public bool SaveFakeCertificates { get; set; } = false;
/// <summary>
/// Overwrite Root certificate file
/// <para>true : replace an existing .pfx file if password is incorect or if RootCertificate = null</para>
/// Overwrite Root certificate file
/// <para>true : replace an existing .pfx file if password is incorect or if RootCertificate = null</para>
/// </summary>
public bool OverwritePfxFile { get; set; } = true;
/// <summary>
/// Minutes certificates should be kept in cache when not used
/// Minutes certificates should be kept in cache when not used
/// </summary>
public int CertificateCacheTimeOutMinutes { get; set; } = 60;
/// <summary>
/// Adjust behaviour when certificates are saved to filesystem
/// Adjust behaviour when certificates are saved to filesystem
/// </summary>
public X509KeyStorageFlags StorageFlag { get; set; } = X509KeyStorageFlags.Exportable;
/// <summary>
/// Constructor.
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <param name="rootCertificateName">Name of root certificate.</param>
/// <param name="rootCertificateIssuerName">Name of root certificate issuer.</param>
/// <param name="userTrustRootCertificate"></param>
/// <param name="machineTrustRootCertificate">Note:setting machineTrustRootCertificate to true will force userTrustRootCertificate to true</param>
/// <param name="trustRootCertificateAsAdmin"></param>
/// <param name="exceptionFunc"></param>
internal CertificateManager(string rootCertificateName, string rootCertificateIssuerName, bool userTrustRootCertificate, bool machineTrustRootCertificate, bool trustRootCertificateAsAdmin, ExceptionHandler exceptionFunc)
public void Dispose()
{
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
string path = Path.GetDirectoryName(assemblyLocation);
if (null == path)
{
throw new NullReferenceException();
}
return path;
}
......@@ -283,7 +292,7 @@ namespace Titanium.Web.Proxy.Network
}
/// <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>
/// <param name="storeLocation"></param>
/// <returns></returns>
......@@ -291,11 +300,12 @@ namespace Titanium.Web.Proxy.Network
{
string value = $"{RootCertificate.Issuer}";
return FindCertificates(StoreName.Root, storeLocation, value).Count > 0
&& (CertificateEngine != CertificateEngine.DefaultWindows
|| FindCertificates(StoreName.My, storeLocation, value).Count > 0);
&& (CertificateEngine != CertificateEngine.DefaultWindows
|| 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);
try
......@@ -310,7 +320,7 @@ namespace Titanium.Web.Proxy.Network
}
/// <summary>
/// Make current machine trust the Root Certificate used by this proxy
/// Make current machine trust the Root Certificate used by this proxy
/// </summary>
/// <param name="storeName"></param>
/// <param name="storeLocation"></param>
......@@ -334,13 +344,13 @@ namespace Titanium.Web.Proxy.Network
{
x509store.Open(OpenFlags.ReadWrite);
x509store.Add(RootCertificate);
}
catch (Exception e)
{
exceptionFunc(
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
{
......@@ -349,13 +359,14 @@ namespace Titanium.Web.Proxy.Network
}
/// <summary>
/// Remove the Root Certificate trust
/// Remove the Root Certificate trust
/// </summary>
/// <param name="storeName"></param>
/// <param name="storeLocation"></param>
/// <param name="certificate"></param>
/// <returns></returns>
private void UninstallCertificate(StoreName storeName, StoreLocation storeLocation, X509Certificate2 certificate)
private void UninstallCertificate(StoreName storeName, StoreLocation storeLocation,
X509Certificate2 certificate)
{
if (certificate == null)
{
......@@ -397,7 +408,6 @@ namespace Titanium.Web.Proxy.Network
if (CertificateEngine == CertificateEngine.DefaultWindows)
{
//prevent certificates getting piled up in User\Personal store
Task.Run(() => UninstallCertificate(StoreName.My, StoreLocation.CurrentUser, certificate));
}
......@@ -405,14 +415,13 @@ namespace Titanium.Web.Proxy.Network
}
/// <summary>
/// Create an SSL certificate
/// Create an SSL certificate
/// </summary>
/// <param name="certificateName"></param>
/// <param name="isRootCertificate"></param>
/// <returns></returns>
internal X509Certificate2 CreateCertificate(string certificateName, bool isRootCertificate)
{
X509Certificate2 certificate = null;
try
{
......@@ -421,7 +430,7 @@ namespace Titanium.Web.Proxy.Network
string path = GetCertificatePath();
string subjectName = ProxyConstants.CNRemoverRegex.Replace(certificateName, string.Empty);
subjectName = subjectName.Replace("*", "$x$");
var certificatePath = Path.Combine(path, subjectName + ".pfx");
string certificatePath = Path.Combine(path, subjectName + ".pfx");
if (!File.Exists(certificatePath))
{
......@@ -434,7 +443,10 @@ namespace Titanium.Web.Proxy.Network
{
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
......@@ -464,7 +476,7 @@ namespace Titanium.Web.Proxy.Network
}
/// <summary>
/// Create an SSL certificate async
/// Create an SSL certificate async
/// </summary>
/// <param name="certificateName"></param>
/// <returns></returns>
......@@ -483,8 +495,6 @@ namespace Titanium.Web.Proxy.Network
Task<X509Certificate2> task;
if (pendingCertificateCreationTasks.TryGetValue(certificateName, out task))
{
//certificate already added to cache
//just return the result here
return await task;
}
......@@ -494,14 +504,12 @@ namespace Titanium.Web.Proxy.Network
var result = CreateCertificate(certificateName, false);
if (result != null)
{
//this is ConcurrentDictionary
//if key exists it will silently handle; no need for locking
certificateCache.TryAdd(certificateName, new CachedCertificate
{
Certificate = result
});
}
return result;
});
pendingCertificateCreationTasks.TryAdd(certificateName, task);
......@@ -511,11 +519,10 @@ namespace Titanium.Web.Proxy.Network
pendingCertificateCreationTasks.TryRemove(certificateName, out task);
return certificate;
}
/// <summary>
/// A method to clear outdated certificates
/// A method to clear outdated certificates
/// </summary>
internal async void ClearIdleCertificates()
{
......@@ -528,7 +535,9 @@ namespace Titanium.Web.Proxy.Network
CachedCertificate removed;
foreach (var cache in outdated)
{
certificateCache.TryRemove(cache.Key, out removed);
}
//after a minute come back to check for outdated certificates in cache
await Task.Delay(1000 * 60);
......@@ -537,7 +546,7 @@ namespace Titanium.Web.Proxy.Network
/// <summary>
/// Stops the certificate cache clear process
/// Stops the certificate cache clear process
/// </summary>
internal void StopClearIdleCertificates()
{
......@@ -545,11 +554,11 @@ namespace Titanium.Web.Proxy.Network
}
/// <summary>
/// Attempts to create a RootCertificate
/// Attempts to create a RootCertificate
/// </summary>
/// <param name="persistToFile">if set to <c>true</c> try to load/save the certificate from rootCert.pfx.</param>
/// <returns>
/// true if succeeded, else false
/// true if succeeded, else false
/// </returns>
public bool CreateRootCertificate(bool persistToFile = true)
{
......@@ -603,7 +612,7 @@ namespace Titanium.Web.Proxy.Network
}
/// <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>
/// <returns></returns>
public X509Certificate2 LoadRootCertificate()
......@@ -627,16 +636,20 @@ namespace Titanium.Web.Proxy.Network
}
/// <summary>
/// Manually load a Root certificate file from give path (.pfx file)
/// </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>
/// Manually load a Root certificate file from give path (.pfx file)
/// </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="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="storageFlag"></param>
/// <returns>
/// true if succeeded, else false
/// true if succeeded, else false
/// </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;
PfxPassword = password;
......@@ -645,16 +658,15 @@ namespace Titanium.Web.Proxy.Network
RootCertificate = LoadRootCertificate();
return (RootCertificate != null);
return RootCertificate != null;
}
/// <summary>
/// Trusts the root certificate in user store, optionally also in machine store
/// Machine trust would require elevated permissions (will silently fail otherwise)
/// Trusts the root certificate in user store, optionally also in machine store
/// Machine trust would require elevated permissions (will silently fail otherwise)
/// </summary>
public void TrustRootCertificate(bool machineTrusted = false)
{
//currentUser\personal
InstallCertificate(StoreName.My, StoreLocation.CurrentUser);
......@@ -674,8 +686,8 @@ namespace Titanium.Web.Proxy.Network
}
/// <summary>
/// Puts the certificate to the user store, optionally also to machine store
/// Prompts with UAC if elevated permissions are required. Works only on Windows.
/// Puts the certificate to the user store, optionally also to machine store
/// Prompts with UAC if elevated permissions are required. Works only on Windows.
/// </summary>
/// <returns></returns>
public bool TrustRootCertificateAsAdmin(bool machineTrusted = false)
......@@ -692,7 +704,7 @@ namespace Titanium.Web.Proxy.Network
File.WriteAllBytes(pfxFileName, RootCertificate.Export(X509ContentType.Pkcs12, PfxPassword));
//currentUser\Root, currentMachine\Personal & currentMachine\Root
var info = new ProcessStartInfo()
var info = new ProcessStartInfo
{
FileName = "certutil.exe",
CreateNoWindow = true,
......@@ -721,7 +733,6 @@ namespace Titanium.Web.Proxy.Network
process.WaitForExit();
File.Delete(pfxFileName);
}
catch (Exception e)
{
......@@ -733,8 +744,8 @@ namespace Titanium.Web.Proxy.Network
}
/// <summary>
/// Ensure certificates are setup (creates root if required)
/// Also makes root certificate trusted based on initial setup from proxy constructor for user/machine trust.
/// Ensure certificates are setup (creates root if required)
/// Also makes root certificate trusted based on initial setup from proxy constructor for user/machine trust.
/// </summary>
public void EnsureRootCertificate()
{
......@@ -751,15 +762,15 @@ namespace Titanium.Web.Proxy.Network
{
TrustRootCertificate(MachineTrustRoot);
}
}
/// <summary>
/// Ensure certificates are setup (creates root if required)
/// Also makes root certificate trusted based on provided parameters
/// Note:setting machineTrustRootCertificate to true will force userTrustRootCertificate to true
/// Ensure certificates are setup (creates root if required)
/// Also makes root certificate trusted based on provided parameters
/// Note:setting machineTrustRootCertificate to true will force userTrustRootCertificate to true
/// </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;
if (machineTrustRootCertificate)
......@@ -775,7 +786,7 @@ namespace Titanium.Web.Proxy.Network
/// <summary>
/// Determines whether the root certificate is trusted.
/// Determines whether the root certificate is trusted.
/// </summary>
public bool IsRootCertificateUserTrusted()
{
......@@ -783,7 +794,7 @@ namespace Titanium.Web.Proxy.Network
}
/// <summary>
/// Determines whether the root certificate is machine trusted.
/// Determines whether the root certificate is machine trusted.
/// </summary>
public bool IsRootCertificateMachineTrusted()
{
......@@ -791,8 +802,8 @@ namespace Titanium.Web.Proxy.Network
}
/// <summary>
/// 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)
/// 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)
/// </summary>
public void RemoveTrustedRootCertificate(bool machineTrusted = false)
{
......@@ -812,12 +823,10 @@ namespace Titanium.Web.Proxy.Network
//this adds to both currentUser\Root & currentMachine\Root
UninstallCertificate(StoreName.Root, StoreLocation.LocalMachine, RootCertificate);
}
}
/// <summary>
/// Removes the trusted certificates from user store, optionally also from machine store
// Prompts with UAC if elevated permissions are required. Works only on Windows.
/// Removes the trusted certificates from user store, optionally also from machine store
/// </summary>
/// <returns></returns>
public bool RemoveTrustedRootCertificateAsAdmin(bool machineTrusted = false)
......@@ -826,14 +835,14 @@ namespace Titanium.Web.Proxy.Network
{
return false;
}
//currentUser\Personal
UninstallCertificate(StoreName.My, StoreLocation.CurrentUser, RootCertificate);
var infos = new List<ProcessStartInfo>();
if (!machineTrusted)
{
//currentUser\Root
infos.Add(new ProcessStartInfo()
infos.Add(new ProcessStartInfo
{
FileName = "certutil.exe",
Arguments = "-delstore -user Root \"" + RootCertificateName + "\"",
......@@ -843,36 +852,38 @@ namespace Titanium.Web.Proxy.Network
ErrorDialog = false,
WindowStyle = ProcessWindowStyle.Hidden
});
}
else
{
infos.AddRange(
new List<ProcessStartInfo>() {
//currentMachine\Personal
new ProcessStartInfo(){
FileName = "certutil.exe",
Arguments = "-delstore My \"" + RootCertificateName + "\"",
CreateNoWindow = true,
UseShellExecute = true,
Verb = "runas",
ErrorDialog = false,
WindowStyle = ProcessWindowStyle.Hidden
},
//currentUser\Personal & currentMachine\Personal
new ProcessStartInfo(){
FileName = "certutil.exe",
Arguments = "-delstore Root \"" + RootCertificateName + "\"",
CreateNoWindow = true,
UseShellExecute = true,
Verb = "runas",
ErrorDialog = false,
WindowStyle = ProcessWindowStyle.Hidden
}
});
new List<ProcessStartInfo>
{
//currentMachine\Personal
new ProcessStartInfo
{
FileName = "certutil.exe",
Arguments = "-delstore My \"" + RootCertificateName + "\"",
CreateNoWindow = true,
UseShellExecute = true,
Verb = "runas",
ErrorDialog = false,
WindowStyle = ProcessWindowStyle.Hidden
},
//currentUser\Personal & currentMachine\Personal
new ProcessStartInfo
{
FileName = "certutil.exe",
Arguments = "-delstore Root \"" + RootCertificateName + "\"",
CreateNoWindow = true,
UseShellExecute = true,
Verb = "runas",
ErrorDialog = false,
WindowStyle = ProcessWindowStyle.Hidden
}
});
}
var success = true;
bool success = true;
try
{
foreach (var info in infos)
......@@ -900,12 +911,5 @@ namespace Titanium.Web.Proxy.Network
certificateCache.Clear();
rootCertificate = null;
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
}
}
}
#if DEBUG
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended.Network;
namespace Titanium.Web.Proxy.Network
{
class DebugCustomBufferedStream : CustomBufferedStream
internal class DebugCustomBufferedStream : CustomBufferedStream
{
private const string basePath = @".";
private static int counter;
public int Counter { get; }
private readonly FileStream fileStreamReceived;
private readonly FileStream fileStreamSent;
private readonly FileStream fileStreamReceived;
public DebugCustomBufferedStream(Stream baseStream, int bufferSize) : base(baseStream, bufferSize)
{
Counter = Interlocked.Increment(ref counter);
......@@ -29,6 +22,8 @@ namespace Titanium.Web.Proxy.Network
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)
{
fileStreamSent.Write(buffer, offset, count);
......@@ -47,4 +42,4 @@ namespace Titanium.Web.Proxy.Network
}
}
}
#endif
\ No newline at end of file
#endif
using StreamExtended.Network;
using System.Net.Sockets;
using System.Net.Sockets;
using StreamExtended.Network;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Network
{
/// <summary>
/// This class wraps Tcp connection to client
/// This class wraps Tcp connection to client
/// </summary>
internal class ProxyClient
{
/// <summary>
/// TcpClient used to communicate with client
/// TcpClient used to communicate with client
/// </summary>
internal TcpClient TcpClient { get; set; }
/// <summary>
/// Holds the stream to client
/// Holds the stream to client
/// </summary>
internal CustomBufferedStream ClientStream { get; set; }
/// <summary>
/// Used to read line by line from client
/// Used to read line by line from client
/// </summary>
internal CustomBinaryReader ClientStreamReader { get; set; }
/// <summary>
/// Used to write line by line to client
/// Used to write line by line to client
/// </summary>
internal HttpResponseWriter ClientStreamWriter { get; set; }
}
......
using StreamExtended.Network;
using System;
using System;
using System.Net;
using System.Net.Sockets;
using StreamExtended.Network;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
......@@ -9,10 +9,17 @@ using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network.Tcp
{
/// <summary>
/// An object that holds TcpConnection to a particular server and port
/// An object that holds TcpConnection to a particular server and port
/// </summary>
internal class TcpConnection : IDisposable
{
internal TcpConnection(ProxyServer proxyServer)
{
LastAccess = DateTime.Now;
this.proxyServer = proxyServer;
this.proxyServer.UpdateServerConnectionCount(true);
}
private ProxyServer proxyServer { get; }
internal ExternalProxy UpStreamProxy { get; set; }
......@@ -26,46 +33,39 @@ namespace Titanium.Web.Proxy.Network.Tcp
internal bool UseUpstreamProxy { get; set; }
/// <summary>
/// Local NIC via connection is made
/// Local NIC via connection is made
/// </summary>
internal IPEndPoint UpStreamEndPoint { get; set; }
/// <summary>
/// Http version
/// Http version
/// </summary>
internal Version Version { get; set; }
internal TcpClient TcpClient { private get; set; }
/// <summary>
/// Used to read lines from server
/// Used to read lines from server
/// </summary>
internal CustomBinaryReader StreamReader { get; set; }
/// <summary>
/// Used to write lines to server
/// Used to write lines to server
/// </summary>
internal HttpRequestWriter StreamWriter { get; set; }
/// <summary>
/// Server stream
/// Server stream
/// </summary>
internal CustomBufferedStream Stream { get; set; }
/// <summary>
/// Last time this connection was used
/// Last time this connection was used
/// </summary>
internal DateTime LastAccess { get; set; }
internal TcpConnection(ProxyServer proxyServer)
{
LastAccess = DateTime.Now;
this.proxyServer = proxyServer;
this.proxyServer.UpdateServerConnectionCount(true);
}
/// <summary>
/// Dispose.
/// Dispose.
/// </summary>
public void Dispose()
{
......
using StreamExtended.Network;
using System;
using System.Linq;
using System;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using StreamExtended.Network;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
......@@ -14,12 +13,12 @@ using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network.Tcp
{
/// <summary>
/// A class that manages Tcp Connection to server used by this proxy server
/// A class that manages Tcp Connection to server used by this proxy server
/// </summary>
internal class TcpConnectionFactory
{
/// <summary>
/// Creates a TCP connection to server
/// Creates a TCP connection to server
/// </summary>
/// <param name="remoteHostName"></param>
/// <param name="remotePort"></param>
......@@ -37,7 +36,8 @@ namespace Titanium.Web.Proxy.Network.Tcp
bool useUpstreamProxy = false;
//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;
......@@ -90,8 +90,8 @@ namespace Titanium.Web.Proxy.Network.Tcp
Response.ParseResponseLine(httpStatus, out _, out int statusCode, out string statusDescription);
if (statusCode != 200 && !statusDescription.EqualsIgnoreCase("OK")
&& !statusDescription.EqualsIgnoreCase("Connection Established"))
if (statusCode != 200 && !statusDescription.EqualsIgnoreCase("OK")
&& !statusDescription.EqualsIgnoreCase("Connection Established"))
{
throw new Exception("Upstream proxy failed to create a secure tunnel");
}
......@@ -102,10 +102,12 @@ namespace Titanium.Web.Proxy.Network.Tcp
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);
await sslStream.AuthenticateAsClientAsync(remoteHostName, null, proxyServer.SupportedSslProtocols, proxyServer.CheckCertificateRevocation);
await sslStream.AuthenticateAsClientAsync(remoteHostName, null, proxyServer.SupportedSslProtocols,
proxyServer.CheckCertificateRevocation);
}
client.ReceiveTimeout = proxyServer.ConnectionTimeOutSeconds * 1000;
......
......@@ -5,13 +5,13 @@ using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Network.Tcp
{
/// <summary>
/// Represents a managed interface of IP Helper API TcpRow struct
/// <see href="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// Represents a managed interface of IP Helper API TcpRow struct
/// <see href="http://msdn2.microsoft.com/en-us/library/aa366913.aspx" />
/// </summary>
internal class TcpRow
{
/// <summary>
/// Initializes a new instance of the <see cref="TcpRow"/> class.
/// Initializes a new instance of the <see cref="TcpRow" /> class.
/// </summary>
/// <param name="tcpRow">TcpRow struct.</param>
internal TcpRow(NativeMethods.TcpRow tcpRow)
......@@ -26,37 +26,37 @@ namespace Titanium.Web.Proxy.Network.Tcp
}
/// <summary>
/// Gets the local end point address.
/// Gets the local end point address.
/// </summary>
internal long LocalAddress { get; }
/// <summary>
/// Gets the local end point port.
/// Gets the local end point port.
/// </summary>
internal int LocalPort { get; }
/// <summary>
/// Gets the local end point.
/// Gets the local end point.
/// </summary>
internal IPEndPoint LocalEndPoint => new IPEndPoint(LocalAddress, LocalPort);
/// <summary>
/// Gets the remote end point address.
/// Gets the remote end point address.
/// </summary>
internal long RemoteAddress { get; }
/// <summary>
/// Gets the remote end point port.
/// Gets the remote end point port.
/// </summary>
internal int RemotePort { get; }
/// <summary>
/// Gets the remote end point.
/// Gets the remote end point.
/// </summary>
internal IPEndPoint RemoteEndPoint => new IPEndPoint(RemoteAddress, RemotePort);
/// <summary>
/// Gets the process identifier.
/// Gets the process identifier.
/// </summary>
internal int ProcessId { get; }
}
......
......@@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Titanium.Web.Proxy.Network.Tcp
{
/// <summary>
/// Represents collection of TcpRows
/// Represents collection of TcpRows
/// </summary>
/// <seealso>
/// <cref>System.Collections.Generic.IEnumerable{Proxy.Tcp.TcpRow}</cref>
......@@ -12,7 +12,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
internal class TcpTable : IEnumerable<TcpRow>
{
/// <summary>
/// Initializes a new instance of the <see cref="TcpTable"/> class.
/// Initializes a new instance of the <see cref="TcpTable" /> class.
/// </summary>
/// <param name="tcpRows">TcpRow collection to initialize with.</param>
internal TcpTable(IEnumerable<TcpRow> tcpRows)
......@@ -21,12 +21,12 @@ namespace Titanium.Web.Proxy.Network.Tcp
}
/// <summary>
/// Gets the TCP rows.
/// Gets the TCP rows.
/// </summary>
internal IEnumerable<TcpRow> TcpRows { get; }
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
public IEnumerator<TcpRow> GetEnumerator()
......@@ -35,7 +35,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
IEnumerator IEnumerable.GetEnumerator()
......
......@@ -5,6 +5,9 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
{
internal class Common
{
internal static uint NewContextAttributes = 0;
internal static SecurityInteger NewLifeTime = new SecurityInteger(0);
#region Private constants
private const int ISC_REQ_REPLAY_DETECT = 0x00000004;
......@@ -14,12 +17,11 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
#endregion
internal static uint NewContextAttributes = 0;
internal static SecurityInteger NewLifeTime = new SecurityInteger(0);
#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 MaximumTokenSize = 12288;
internal const int SecurityCredentialsOutbound = 2;
......@@ -69,7 +71,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
Negotiate128 = 0x20000000,
// Indicates that this client supports medium (56-bit) encryption.
Negotiate56 = (unchecked((int)0x80000000))
Negotiate56 = unchecked((int)0x80000000)
}
internal enum NtlmAuthLevel
......@@ -86,7 +88,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
NTLM_only,
/* Use NTLMv2 only. */
NTLMv2_only,
NTLMv2_only
}
#endregion
......@@ -105,7 +107,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
}
/// <summary>
/// Resets all internal pointers to default value
/// Resets all internal pointers to default value
/// </summary>
internal void Reset()
{
......@@ -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
//SecBuffer and release it...
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);
}
}
......@@ -267,13 +270,14 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
//byte array...
int currentOffset = index * Marshal.SizeOf(typeof(Buffer));
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);
bufferIndex += bytesToCopy;
}
}
return (buffer);
return buffer;
}
}
......
......@@ -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)
{
......@@ -47,7 +47,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
return new[] { bytes[1], bytes[0] };
}
private static unsafe byte[] GetUIntBytes(byte*bytes)
private static unsafe byte[] GetUIntBytes(byte* bytes)
{
if (BitConverter.IsLittleEndian)
{
......@@ -57,7 +57,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
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)
{
......@@ -117,7 +117,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
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)
{
......@@ -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)
{
......@@ -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)
{
for (int i = 0; i < 8; ++i)
{
dst[i] = src[startIndex + i];
}
}
else
{
for (int i = 0; i < 8; ++i)
{
dst[i] = src[startIndex + (7 - i)];
}
}
}
......
......@@ -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 readonly int type;
internal Message(byte[] message)
{
type = 3;
......@@ -49,17 +51,15 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
}
/// <summary>
/// Domain name
/// Domain name
/// </summary>
internal string Domain { get; private set; }
/// <summary>
/// Username
/// Username
/// </summary>
internal string Username { get; private set; }
private readonly int type;
internal Common.NtlmFlags Flags { get; set; }
// methods
......@@ -68,7 +68,9 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
//base.Decode (message);
if (message == null)
{
throw new ArgumentNullException("message");
}
if (message.Length < 12)
{
......@@ -108,12 +110,13 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
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)
{
return Encoding.Unicode.GetString(buffer, offset, len);
}
return Encoding.ASCII.GetString(buffer, offset, len);
}
......@@ -122,9 +125,12 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
for (int i = 0; i < header.Length; i++)
{
if (message[i] != header[i])
{
return false;
}
}
return (LittleEndian.ToUInt32(message, 8) == type);
return LittleEndian.ToUInt32(message, 8) == type;
}
}
}
......@@ -3,12 +3,12 @@
namespace Titanium.Web.Proxy.Network.WinAuth.Security
{
/// <summary>
/// Status of authenticated session
/// Status of authenticated session
/// </summary>
internal class State
{
/// <summary>
/// States during Windows Authentication
/// States during Windows Authentication
/// </summary>
public enum WinAuthState
{
......@@ -16,36 +16,36 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
INITIAL_TOKEN,
FINAL_TOKEN,
AUTHORIZED
};
internal State()
{
Credentials = new Common.SecurityHandle(0);
Context = new Common.SecurityHandle(0);
LastSeen = DateTime.Now;
AuthState = WinAuthState.UNAUTHORIZED;
}
/// <summary>
/// Credentials used to validate NTLM hashes
/// Current state of the authentication process
/// </summary>
internal Common.SecurityHandle Credentials;
internal WinAuthState AuthState;
/// <summary>
/// Context will be used to validate HTLM hashes
/// Context will be used to validate HTLM hashes
/// </summary>
internal Common.SecurityHandle Context;
/// <summary>
/// Timestamp needed to calculate validity of the authenticated session
/// Credentials used to validate NTLM hashes
/// </summary>
internal DateTime LastSeen;
internal Common.SecurityHandle Credentials;
/// <summary>
/// Current state of the authentication process
/// Timestamp needed to calculate validity of the authenticated session
/// </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()
{
......
......@@ -15,13 +15,13 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
internal class WinAuthEndPoint
{
/// <summary>
/// Keep track of auth states for reuse in final challenge response
/// Keep track of auth states for reuse in final challenge response
/// </summary>
private static readonly IDictionary<Guid, State> authStates = new ConcurrentDictionary<Guid, State>();
/// <summary>
/// Acquire the intial client token to send
/// Acquire the intial client token to send
/// </summary>
/// <param name="hostname"></param>
/// <param name="authScheme"></param>
......@@ -53,7 +53,6 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
if (result != SuccessfulResult)
{
// Credentials acquire operation failed.
return null;
}
......@@ -73,7 +72,6 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
if (result != IntermediateResult)
{
// Client challenge issue operation failed.
return null;
}
......@@ -91,7 +89,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
}
/// <summary>
/// Acquire the final token to send
/// Acquire the final token to send
/// </summary>
/// <param name="hostname"></param>
/// <param name="serverChallenge"></param>
......@@ -128,7 +126,6 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
if (result != SuccessfulResult)
{
// Client challenge issue operation failed.
return null;
}
......@@ -145,7 +142,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
}
/// <summary>
/// Clear any hanging states
/// Clear any hanging states
/// </summary>
/// <param name="stateCacheTimeOutMinutes"></param>
internal static async void ClearIdleStates(int stateCacheTimeOutMinutes)
......@@ -164,37 +161,37 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
}
/// <summary>
/// Validates that the current WinAuth state of the connection matches the
/// expectation, used to detect failed authentication
/// Validates that the current WinAuth state of the connection matches the
/// expectation, used to detect failed authentication
/// </summary>
/// <param name="requestId"></param>
/// <param name="expectedAuthState"></param>
/// <returns></returns>
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)
{
// Validation before initial token
return stateExists == false ||
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)
{
// Validation before final token
return stateExists &&
(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");
}
/// <summary>
/// Set the AuthState to authorized and update the connection state lifetime
/// Set the AuthState to authorized and update the connection state lifetime
/// </summary>
/// <param name="requestId"></param>
internal static void AuthenticatedResponse(Guid requestId)
......@@ -209,7 +206,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
#region Native calls to secur32.dll
[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
string pszTargetName,
int fContextReq,
......@@ -223,7 +220,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
out SecurityInteger ptsExpiry); //PTimeStamp
[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
string pszTargetName,
int fContextReq,
......
......@@ -4,15 +4,15 @@ using Titanium.Web.Proxy.Network.WinAuth.Security;
namespace Titanium.Web.Proxy.Network.WinAuth
{
/// <summary>
/// A handler for NTLM/Kerberos windows authentication challenge from server
/// NTLM process details below
/// https://blogs.msdn.microsoft.com/chiranth/2013/09/20/ntlm-want-to-know-how-it-works/
/// A handler for NTLM/Kerberos windows authentication challenge from server
/// NTLM process details below
/// https://blogs.msdn.microsoft.com/chiranth/2013/09/20/ntlm-want-to-know-how-it-works/
/// </summary>
public static class WinAuthHandler
{
/// <summary>
/// Get the initial client token for server
/// using credentials of user running the proxy server process
/// Get the initial client token for server
/// using credentials of user running the proxy server process
/// </summary>
/// <param name="serverHostname"></param>
/// <param name="authScheme"></param>
......@@ -26,7 +26,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth
/// <summary>
/// Get the final token given the server challenge token
/// Get the final token given the server challenge token
/// </summary>
/// <param name="serverHostname"></param>
/// <param name="serverToken"></param>
......@@ -34,7 +34,9 @@ namespace Titanium.Web.Proxy.Network.WinAuth
/// <returns></returns>
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));
}
......
......@@ -32,7 +32,8 @@ namespace Titanium.Web.Proxy
}
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
session.WebSession.Response = CreateAuthentication407Response("Proxy Authentication Invalid");
......@@ -53,7 +54,6 @@ namespace Titanium.Web.Proxy
bool authenticated = await AuthenticateUserFunc(username, password);
if (!authenticated)
{
//Return not authorized
session.WebSession.Response = CreateAuthentication407Response("Proxy Authentication Invalid");
}
......@@ -61,7 +61,8 @@ namespace Titanium.Web.Proxy
}
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
session.WebSession.Response = CreateAuthentication407Response("Proxy Authentication Invalid");
......
......@@ -26,99 +26,143 @@ namespace Titanium.Web.Proxy
internal static readonly string UriSchemeHttps = Uri.UriSchemeHttps;
/// <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>
private int clientConnectionCount;
/// <summary>
/// Backing field for corresponding public property
/// backing exception func for exposed public property
/// </summary>
private int serverConnectionCount;
private ExceptionHandler exceptionFunc;
/// <summary>
/// A object that creates tcp connection to server
/// Backing field for corresponding public property
/// </summary>
private TcpConnectionFactory tcpConnectionFactory { get; }
private int serverConnectionCount;
/// <summary>
/// Manaage upstream proxy detection
/// Manaage upstream proxy detection
/// </summary>
private WinHttpWebProxyFinder systemProxyResolver;
/// <summary>
/// Manage system proxy settings
/// Constructor
/// </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>
/// An default exception log func
/// Constructor.
/// </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>
/// backing exception func for exposed public property
/// A object that creates tcp connection to server
/// </summary>
private ExceptionHandler exceptionFunc;
private TcpConnectionFactory tcpConnectionFactory { get; }
/// <summary>
/// Manage system proxy settings
/// </summary>
private SystemProxyManager systemProxySettingsManager { get; }
/// <summary>
/// Is the proxy currently running
/// Is the proxy currently running
/// </summary>
public bool ProxyRunning { get; private set; }
/// <summary>
/// Gets or sets a value indicating whether requests will be chained to upstream gateway.
/// Gets or sets a value indicating whether requests will be chained to upstream gateway.
/// </summary>
public bool ForwardToUpstreamGateway { get; set; }
/// <summary>
/// Enable disable Windows Authentication (NTLM/Kerberos)
/// Note: NTLM/Kerberos will always send local credentials of current user
/// who is running the proxy process. This is because a man
/// in middle attack is not currently supported
/// (which would require windows delegation enabled for this server process)
/// Enable disable Windows Authentication (NTLM/Kerberos)
/// Note: NTLM/Kerberos will always send local credentials of current user
/// who is running the proxy process. This is because a man
/// in middle attack is not currently supported
/// (which would require windows delegation enabled for this server process)
/// </summary>
public bool EnableWinAuth { get; set; }
/// <summary>
/// Should we check for certificare revocation during SSL authentication to servers
/// Note: If enabled can reduce performance (Default disabled)
/// Should we check for certificare revocation during SSL authentication to servers
/// Note: If enabled can reduce performance (Default disabled)
/// </summary>
public bool CheckCertificateRevocation { get; set; }
/// <summary>
/// Does this proxy uses the HTTP protocol 100 continue behaviour strictly?
/// Broken 100 contunue implementations on server/client may cause problems if enabled
/// Does this proxy uses the HTTP protocol 100 continue behaviour strictly?
/// Broken 100 contunue implementations on server/client may cause problems if enabled
/// </summary>
public bool Enable100ContinueBehaviour { get; set; }
/// <summary>
/// Buffer size used throughout this proxy
/// Buffer size used throughout this proxy
/// </summary>
public int BufferSize { get; set; } = 8192;
/// <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>
public int ConnectionTimeOutSeconds { get; set; }
/// <summary>
/// Total number of active client connections
/// Total number of active client connections
/// </summary>
public int ClientConnectionCount => clientConnectionCount;
/// <summary>
/// Total number of active server connections
/// Total number of active server connections
/// </summary>
public int ServerConnectionCount => serverConnectionCount;
/// <summary>
/// Realm used during Proxy Basic Authentication
/// Realm used during Proxy Basic Authentication
/// </summary>
public string ProxyRealm { get; set; } = "TitaniumProxy";
/// <summary>
/// List of supported Ssl versions
/// List of supported Ssl versions
/// </summary>
public SslProtocols SupportedSslProtocols { get; set; } =
#if NET45
......@@ -128,128 +172,109 @@ namespace Titanium.Web.Proxy
/// <summary>
/// Manages certificates used by this proxy
/// Manages certificates used by this proxy
/// </summary>
public CertificateManager CertificateManager { get; }
/// <summary>
/// External proxy for Http
/// External proxy for Http
/// </summary>
public ExternalProxy UpStreamHttpProxy { get; set; }
/// <summary>
/// External proxy for Http
/// External proxy for Http
/// </summary>
public ExternalProxy UpStreamHttpsProxy { get; set; }
/// <summary>
/// Local adapter/NIC endpoint (where proxy makes request via)
/// default via any IP addresses of this machine
/// Local adapter/NIC endpoint (where proxy makes request via)
/// default via any IP addresses of this machine
/// </summary>
public IPEndPoint UpStreamEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0);
/// <summary>
/// A list of IpAddress and port this proxy is listening to
/// A list of IpAddress and port this proxy is listening to
/// </summary>
public List<ProxyEndPoint> ProxyEndPoints { get; set; }
/// <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>
public event EventHandler ClientConnectionCountChanged;
public Func<SessionEventArgsBase, Task<ExternalProxy>> GetCustomUpStreamProxyFunc { get; set; }
/// <summary>
/// Occurs when server connection count changed.
/// Callback for error events in proxy
/// </summary>
public event EventHandler ServerConnectionCountChanged;
public ExceptionHandler ExceptionFunc
{
get => exceptionFunc ?? defaultExceptionFunc.Value;
set => exceptionFunc = value;
}
/// <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>
public event AsyncEventHandler<CertificateValidationEventArgs> ServerCertificateValidationCallback;
public Func<string, string, Task<bool>> AuthenticateUserFunc { get; set; }
/// <summary>
/// Callback tooverride client certificate during SSL mutual authentication
/// Dispose Proxy.
/// </summary>
public event AsyncEventHandler<CertificateSelectionEventArgs> ClientCertificateSelectionCallback;
public void Dispose()
{
if (ProxyRunning)
{
Stop();
}
/// <summary>
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP(S) requests
/// return the ExternalProxy object with valid credentials
/// </summary>
public Func<SessionEventArgsBase, Task<ExternalProxy>> GetCustomUpStreamProxyFunc { get; set; }
CertificateManager?.Dispose();
}
/// <summary>
/// Intercept request to server
/// Occurs when client connection count changed.
/// </summary>
public event AsyncEventHandler<SessionEventArgs> BeforeRequest;
public event EventHandler ClientConnectionCountChanged;
/// <summary>
/// Intercept response from server
/// Occurs when server connection count changed.
/// </summary>
public event AsyncEventHandler<SessionEventArgs> BeforeResponse;
public event EventHandler ServerConnectionCountChanged;
/// <summary>
/// Intercept after response from server
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication
/// </summary>
public event AsyncEventHandler<SessionEventArgs> AfterResponse;
public event AsyncEventHandler<CertificateValidationEventArgs> ServerCertificateValidationCallback;
/// <summary>
/// Callback for error events in proxy
/// Callback tooverride client certificate during SSL mutual authentication
/// </summary>
public ExceptionHandler ExceptionFunc
{
get => exceptionFunc ?? defaultExceptionFunc.Value;
set => exceptionFunc = value;
}
public event AsyncEventHandler<CertificateSelectionEventArgs> ClientCertificateSelectionCallback;
/// <summary>
/// A callback to authenticate clients
/// Parameters are username, password provided by client
/// return true for successful authentication
/// Intercept request to server
/// </summary>
public Func<string, string, Task<bool>> AuthenticateUserFunc { get; set; }
public event AsyncEventHandler<SessionEventArgs> BeforeRequest;
/// <summary>
/// Constructor
/// Intercept response from server
/// </summary>
/// <param name="userTrustRootCertificate"></param>
/// <param name="machineTrustRootCertificate">Note:setting machineTrustRootCertificate to true will force userTrustRootCertificate to true</param>
/// <param name="trustRootCertificateAsAdmin"></param>
public ProxyServer(bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false, bool trustRootCertificateAsAdmin = false) : this(null, null, userTrustRootCertificate, machineTrustRootCertificate, trustRootCertificateAsAdmin)
{
}
public event AsyncEventHandler<SessionEventArgs> BeforeResponse;
/// <summary>
/// Constructor.
/// Intercept after response from server
/// </summary>
/// <param name="rootCertificateName">Name of root certificate.</param>
/// <param name="rootCertificateIssuerName">Name of root certificate issuer.</param>
/// <param name="userTrustRootCertificate"></param>
/// <param name="machineTrustRootCertificate">Note:setting machineTrustRootCertificate to true will force userTrustRootCertificate to true</param>
/// <param name="trustRootCertificateAsAdmin"></param>
public ProxyServer(string rootCertificateName, string rootCertificateIssuerName, bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false, bool trustRootCertificateAsAdmin = false)
{
//default values
ConnectionTimeOutSeconds = 30;
ProxyEndPoints = new List<ProxyEndPoint>();
tcpConnectionFactory = new TcpConnectionFactory();
if (!RunTime.IsRunningOnMono && RunTime.IsWindows)
{
systemProxySettingsManager = new SystemProxyManager();
}
CertificateManager = new CertificateManager(rootCertificateName, rootCertificateIssuerName, userTrustRootCertificate, machineTrustRootCertificate, trustRootCertificateAsAdmin, ExceptionFunc);
}
public event AsyncEventHandler<SessionEventArgs> AfterResponse;
/// <summary>
/// Add a proxy end point
/// Add a proxy end point
/// </summary>
/// <param name="endPoint"></param>
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");
}
......@@ -263,8 +288,8 @@ namespace Titanium.Web.Proxy
}
/// <summary>
/// Remove a proxy end point
/// Will throw error if the end point does'nt exist
/// Remove a proxy end point
/// Will throw error if the end point does'nt exist
/// </summary>
/// <param name="endPoint"></param>
public void RemoveEndPoint(ProxyEndPoint endPoint)
......@@ -283,7 +308,7 @@ namespace Titanium.Web.Proxy
}
/// <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>
/// <param name="endPoint"></param>
public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint)
......@@ -292,7 +317,7 @@ namespace Titanium.Web.Proxy
}
/// <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>
/// <param name="endPoint"></param>
public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint)
......@@ -301,7 +326,7 @@ namespace Titanium.Web.Proxy
}
/// <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>
/// <param name="endPoint"></param>
/// <param name="protocolType"></param>
......@@ -316,10 +341,9 @@ namespace Titanium.Web.Proxy
bool isHttp = (protocolType & ProxyProtocolType.Http) > 0;
bool isHttps = (protocolType & ProxyProtocolType.Https) > 0;
if (isHttps)
{
CertificateManager.EnsureRootCertificate();
//If certificate was trusted by the machine
......@@ -375,12 +399,13 @@ namespace Titanium.Web.Proxy
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>
/// Remove any HTTP proxy setting of current machien
/// Remove any HTTP proxy setting of current machien
/// </summary>
public void DisableSystemHttpProxy()
{
......@@ -388,7 +413,7 @@ namespace Titanium.Web.Proxy
}
/// <summary>
/// Remove any HTTPS proxy setting for current machine
/// Remove any HTTPS proxy setting for current machine
/// </summary>
public void DisableSystemHttpsProxy()
{
......@@ -396,7 +421,7 @@ namespace Titanium.Web.Proxy
}
/// <summary>
/// Remove the specified proxy settings for current machine
/// Remove the specified proxy settings for current machine
/// </summary>
public void DisableSystemProxy(ProxyProtocolType protocolType)
{
......@@ -409,7 +434,7 @@ namespace Titanium.Web.Proxy
}
/// <summary>
/// Clear all proxy settings for current machine
/// Clear all proxy settings for current machine
/// </summary>
public void DisableAllSystemProxies()
{
......@@ -422,7 +447,7 @@ namespace Titanium.Web.Proxy
}
/// <summary>
/// Start this proxy server
/// Start this proxy server
/// </summary>
public void Start()
{
......@@ -447,7 +472,7 @@ namespace Titanium.Web.Proxy
foreach (var proxy in proxyInfo.Proxies.Values)
{
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))
{
protocolToRemove |= proxy.ProtocolType;
......@@ -456,7 +481,6 @@ namespace Titanium.Web.Proxy
if (protocolToRemove != ProxyProtocolType.None)
{
//do not restore to any of listening address when we quit
systemProxySettingsManager.RemoveProxy(protocolToRemove, false);
}
}
......@@ -482,13 +506,12 @@ namespace Titanium.Web.Proxy
if (RunTime.IsWindows && !RunTime.IsRunningOnMono)
{
//clear orphaned windows auth states every 2 minutes
WinAuthEndPoint.ClearIdleStates(2);
}
}
/// <summary>
/// Stop this proxy server
/// Stop this proxy server
/// </summary>
public void Stop()
{
......@@ -499,7 +522,8 @@ namespace Titanium.Web.Proxy
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)
{
......@@ -520,20 +544,7 @@ namespace Titanium.Web.Proxy
}
/// <summary>
/// Dispose Proxy.
/// </summary>
public void Dispose()
{
if (ProxyRunning)
{
Stop();
}
CertificateManager?.Dispose();
}
/// <summary>
/// Listen on the given end point on local machine
/// Listen on the given end point on local machine
/// </summary>
/// <param name="endPoint"></param>
private void Listen(ProxyEndPoint endPoint)
......@@ -550,7 +561,8 @@ namespace Titanium.Web.Proxy
}
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("port", endPoint.Port);
throw pex;
......@@ -558,13 +570,16 @@ namespace Titanium.Web.Proxy
}
/// <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>
/// <param name="endPoint"></param>
private void ValidateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint)
{
if (endPoint == null)
{
throw new ArgumentNullException(nameof(endPoint));
}
if (ProxyEndPoints.Contains(endPoint) == false)
{
throw new Exception("Cannot set endPoints not added to proxy as system proxy");
......@@ -577,10 +592,13 @@ namespace Titanium.Web.Proxy
}
/// <summary>
/// Gets the system up stream proxy.
/// Gets the system up stream proxy.
/// </summary>
/// <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>
/// <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>
private Task<ExternalProxy> GetSystemUpStreamProxy(SessionEventArgsBase sessionEventArgs)
{
var proxy = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri);
......@@ -588,7 +606,7 @@ namespace Titanium.Web.Proxy
}
/// <summary>
/// When a connection is received from client act
/// When a connection is received from client act
/// </summary>
/// <param name="asyn"></param>
private void OnAcceptConnection(IAsyncResult asyn)
......@@ -616,10 +634,7 @@ namespace Titanium.Web.Proxy
if (tcpClient != null)
{
Task.Run(async () =>
{
await HandleClient(tcpClient, endPoint);
});
Task.Run(async () => { await HandleClient(tcpClient, endPoint); });
}
// Get the listener that handles the client request.
......@@ -652,7 +667,7 @@ namespace Titanium.Web.Proxy
}
/// <summary>
/// Quit listening on the given end point
/// Quit listening on the given end point
/// </summary>
/// <param name="endPoint"></param>
private void QuitListen(ProxyEndPoint endPoint)
......
......@@ -2,6 +2,7 @@
using System.Net;
using System.Net.Sockets;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended.Network;
using Titanium.Web.Proxy.EventArguments;
......@@ -11,23 +12,24 @@ using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.Tcp;
using System.Threading;
namespace Titanium.Web.Proxy
{
/// <summary>
/// Handle the request
/// Handle the request
/// </summary>
partial class ProxyServer
{
private static readonly Regex uriSchemeRegex = new Regex("^[a-z]*://", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static readonly Regex uriSchemeRegex =
new Regex("^[a-z]*://", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private bool isWindowsAuthenticationEnabledAndSupported => EnableWinAuth && RunTime.IsWindows && !RunTime.IsRunningOnMono;
private bool isWindowsAuthenticationEnabledAndSupported =>
EnableWinAuth && RunTime.IsWindows && !RunTime.IsRunningOnMono;
/// <summary>
/// This is the core request handler method for a particular connection from client
/// Will create new session (request/response) sequence until
/// client/server abruptly terminates connection or by normal HTTP termination
/// This is the core request handler method for a particular connection from client
/// Will create new session (request/response) sequence until
/// client/server abruptly terminates connection or by normal HTTP termination
/// </summary>
/// <param name="client"></param>
/// <param name="clientStream"></param>
......@@ -40,8 +42,10 @@ namespace Titanium.Web.Proxy
/// <param name="isTransparentEndPoint"></param>
/// <returns></returns>
private async Task HandleHttpSessionRequest(ProxyEndPoint endPoint, TcpClient client,
CustomBufferedStream clientStream, CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter,
CancellationTokenSource cancellationTokenSource, string httpsConnectHostname, ConnectRequest connectRequest, bool isTransparentEndPoint = false)
CustomBufferedStream clientStream, CustomBinaryReader clientStreamReader,
HttpResponseWriter clientStreamWriter,
CancellationTokenSource cancellationTokenSource, string httpsConnectHostname, ConnectRequest connectRequest,
bool isTransparentEndPoint = false)
{
TcpConnection connection = null;
......@@ -68,7 +72,8 @@ namespace Titanium.Web.Proxy
{
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
await HeaderParser.ReadHeaders(clientStreamReader, args.WebSession.Request.Headers);
......@@ -94,7 +99,8 @@ namespace Titanium.Web.Proxy
hostAndPath += httpUrl;
}
string url = string.Concat(httpsConnectHostname == null ? "http://" : "https://", hostAndPath);
string url = string.Concat(httpsConnectHostname == null ? "http://" : "https://",
hostAndPath);
try
{
httpRemoteUri = new Uri(url);
......@@ -116,7 +122,8 @@ namespace Titanium.Web.Proxy
args.ProxyClient.ClientStreamWriter = clientStreamWriter;
//proxy authorization check
if (!args.IsTransparent && httpsConnectHostname == null && await CheckAuthorization(args) == false)
if (!args.IsTransparent && httpsConnectHostname == null &&
await CheckAuthorization(args) == false)
{
await InvokeBeforeResponse(args);
......@@ -163,9 +170,10 @@ namespace Titanium.Web.Proxy
//create a new connection if hostname/upstream end point changes
if (connection != null
&& (!connection.HostName.Equals(request.RequestUri.Host, StringComparison.OrdinalIgnoreCase)
|| (args.WebSession.UpStreamEndPoint != null
&& !args.WebSession.UpStreamEndPoint.Equals(connection.UpStreamEndPoint))))
&& (!connection.HostName.Equals(request.RequestUri.Host,
StringComparison.OrdinalIgnoreCase)
|| args.WebSession.UpStreamEndPoint != null
&& !args.WebSession.UpStreamEndPoint.Equals(connection.UpStreamEndPoint)))
{
connection.Dispose();
connection = null;
......@@ -184,7 +192,8 @@ namespace Titanium.Web.Proxy
await connection.StreamWriter.WriteHeadersAsync(request.Headers);
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);
response.HttpVersion = responseVersion;
response.StatusCode = responseStatusCode;
......@@ -216,7 +225,6 @@ namespace Titanium.Web.Proxy
if (args.WebSession.ServerConnection == null)
{
//server connection was closed
return;
}
......@@ -246,7 +254,6 @@ namespace Titanium.Web.Proxy
await InvokeAfterResponse(args);
args.Dispose();
}
}
}
finally
......@@ -256,7 +263,7 @@ namespace Titanium.Web.Proxy
}
/// <summary>
/// Handle a specific session (request/response sequence)
/// Handle a specific session (request/response sequence)
/// </summary>
/// <param name="connection"></param>
/// <param name="args"></param>
......@@ -285,12 +292,14 @@ namespace Titanium.Web.Proxy
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();
}
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();
}
}
......@@ -305,7 +314,6 @@ namespace Titanium.Web.Proxy
//check if content-length is > 0
if (request.ContentLength > 0)
{
//If request was modified by user
if (request.IsBodyRead)
{
var writer = args.WebSession.ServerConnection.StreamWriter;
......@@ -315,7 +323,6 @@ namespace Titanium.Web.Proxy
{
if (!request.ExpectationFailed)
{
//If its a post/put/patch request, then read the client html body and send it to server
if (request.HasBody)
{
HttpWriter writer = args.WebSession.ServerConnection.StreamWriter;
......@@ -338,7 +345,7 @@ namespace Titanium.Web.Proxy
}
/// <summary>
/// Create a Server Connection
/// Create a Server Connection
/// </summary>
/// <param name="args"></param>
/// <param name="isConnect"></param>
......@@ -365,7 +372,7 @@ namespace Titanium.Web.Proxy
}
/// <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>
/// <param name="requestHeaders"></param>
private void PrepareRequestHeaders(HeaderCollection requestHeaders)
......
......@@ -9,12 +9,12 @@ using Titanium.Web.Proxy.Network.WinAuth.Security;
namespace Titanium.Web.Proxy
{
/// <summary>
/// Handle the response from server
/// Handle the response from server
/// </summary>
partial class ProxyServer
{
/// <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>
/// <param name="args"></param>
/// <returns>true if client/server connection was terminated (and disposed) </returns>
......@@ -87,12 +87,14 @@ namespace Titanium.Web.Proxy
//Write back to client 100-conitinue response if that's what server returned
if (response.Is100Continue)
{
await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, (int)HttpStatusCode.Continue, "Continue");
await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion,
(int)HttpStatusCode.Continue, "Continue");
await clientStreamWriter.WriteLineAsync();
}
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();
}
......@@ -108,7 +110,8 @@ namespace Titanium.Web.Proxy
else
{
//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);
//Write body if exists
......
......@@ -3,7 +3,7 @@
namespace Titanium.Web.Proxy.Shared
{
/// <summary>
/// Literals shared by Proxy Server
/// Literals shared by Proxy Server
/// </summary>
internal class ProxyConstants
{
......@@ -14,8 +14,9 @@ namespace Titanium.Web.Proxy.Shared
internal static readonly char[] SemiColonSplit = { ';' };
internal static readonly char[] EqualSplit = { '=' };
internal static readonly byte[] NewLine = {(byte)'\r', (byte)'\n' };
internal static readonly 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 @@
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended;
using StreamExtended.Helpers;
......@@ -10,16 +11,14 @@ using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using System.Threading;
namespace Titanium.Web.Proxy
{
partial class ProxyServer
{
/// <summary>
/// This is called when this proxy acts as a reverse proxy (like a real http server)
/// So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
/// This is called when this proxy acts as a reverse proxy (like a real http server)
/// So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
/// </summary>
/// <param name="endPoint"></param>
/// <param name="tcpClient"></param>
......@@ -32,12 +31,12 @@ namespace Titanium.Web.Proxy
var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
try
{
var clientHelloInfo = await SslTools.PeekClientHello(clientStream);
var isHttps = clientHelloInfo != null;
bool isHttps = clientHelloInfo != null;
string httpsHostName = null;
if (isHttps)
......@@ -76,7 +75,8 @@ namespace Titanium.Web.Proxy
}
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();
return;
}
......@@ -136,4 +136,4 @@ namespace Titanium.Web.Proxy
}
}
}
}
\ No newline at end of file
}
......@@ -32,13 +32,13 @@ namespace Titanium.Web.Proxy
};
/// <summary>
/// Handle windows NTLM authentication
/// Can expand this for Kerberos in future
/// Note: NTLM/Kerberos cannot do a man in middle operation
/// we do for HTTPS requests.
/// As such we will be sending local credentials of current
/// User to server to authenticate requests.
/// To disable this set ProxyServer.EnableWinAuth to false
/// Handle windows NTLM authentication
/// Can expand this for Kerberos in future
/// Note: NTLM/Kerberos cannot do a man in middle operation
/// we do for HTTPS requests.
/// As such we will be sending local credentials of current
/// User to server to authenticate requests.
/// To disable this set ProxyServer.EnableWinAuth to false
/// </summary>
internal async Task Handle401UnAuthorized(SessionEventArgs args)
{
......@@ -58,7 +58,8 @@ namespace Titanium.Web.Proxy
if (headerName != null)
{
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
......@@ -87,7 +88,8 @@ namespace Titanium.Web.Proxy
{
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))
{
......@@ -120,8 +122,9 @@ namespace Titanium.Web.Proxy
//challenge value will start with any of the scheme selected
else
{
scheme = authSchemes.First(x => authHeader.Value.StartsWith(x, StringComparison.OrdinalIgnoreCase) &&
authHeader.Value.Length > x.Length + 1);
scheme = authSchemes.First(x =>
authHeader.Value.StartsWith(x, StringComparison.OrdinalIgnoreCase) &&
authHeader.Value.Length > x.Length + 1);
string serverToken = authHeader.Value.Substring(scheme.Length + 1);
string clientToken = WinAuthHandler.GetFinalAuthToken(request.Host, serverToken, args.Id);
......@@ -148,7 +151,7 @@ namespace Titanium.Web.Proxy
}
/// <summary>
/// Rewrites the response body for failed authentication
/// Rewrites the response body for failed authentication
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
......@@ -157,32 +160,35 @@ namespace Titanium.Web.Proxy
var response = args.WebSession.Response;
// 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);
}
// 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 (" +
args.ProxyClient.TcpClient.Client.LocalEndPoint +
") failed. Please check credentials.</h2></div>";
string originalErrorMessage = "<div class=\"inserted-by-proxy\"><h3>Response from remote web server below.</h3></div><br/>";
string authErrorMessage =
"<div class=\"inserted-by-proxy\"><h2>NTLM authentication through Titanium.Web.Proxy (" +
args.ProxyClient.TcpClient.Client.LocalEndPoint +
") 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();
int idx = body.IndexOfIgnoreCase("<body>");
if (idx >= 0)
{
var bodyPos = idx + "<body>".Length;
int bodyPos = idx + "<body>".Length;
body = body.Insert(bodyPos, authErrorMessage + originalErrorMessage);
}
else
{
// 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\">" +
"<html xmlns=\"http://www.w3.org/1999/xhtml\">" +
"<body>" +
authErrorMessage +
"</body>" +
"</html>";
body =
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">" +
"<html xmlns=\"http://www.w3.org/1999/xhtml\">" +
"<body>" +
authErrorMessage +
"</body>" +
"</html>";
}
args.SetResponseBodyString(body);
......
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
</assemblyBinding>
</runtime>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
</configuration>
</configuration>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Portable.BouncyCastle" version="1.8.1.4" 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