Commit 703f5219 authored by Jehonathan's avatar Jehonathan Committed by GitHub

Merge pull request #80 from justcoding121/cleanup

Clean up
parents a4e2e3ed 3042d211
using System;
using System.Linq;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
namespace Titanium.Web.Proxy
{
public partial class ProxyServer
{
/// <summary>
/// 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 static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
//if user callback is registered then do it
if (ServerCertificateValidationCallback != null)
{
var args = new CertificateValidationEventArgs();
args.Certificate = certificate;
args.Chain = chain;
args.SslPolicyErrors = sslPolicyErrors;
Delegate[] invocationList = ServerCertificateValidationCallback.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, CertificateValidationEventArgs, Task>)invocationList[i])(null, args);
}
Task.WhenAll(handlerTasks).Wait();
return args.IsValid;
}
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
//By default
//do not allow this client to communicate with unauthenticated servers.
return false;
}
/// <summary>
/// Call back to select client certificate used for mutual authentication
/// </summary>
/// <param name="sender"></param>
/// <param name="certificate"></param>
/// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param>
/// <returns></returns>
internal static X509Certificate SelectClientCertificate(
object sender,
string targetHost,
X509CertificateCollection localCertificates,
X509Certificate remoteCertificate,
string[] acceptableIssuers)
{
X509Certificate clientCertificate = null;
var customSslStream = sender as SslStream;
if (acceptableIssuers != null &&
acceptableIssuers.Length > 0 &&
localCertificates != null &&
localCertificates.Count > 0)
{
// Use the first certificate that is from an acceptable issuer.
foreach (X509Certificate certificate in localCertificates)
{
string issuer = certificate.Issuer;
if (Array.IndexOf(acceptableIssuers, issuer) != -1)
clientCertificate = certificate;
}
}
if (localCertificates != null &&
localCertificates.Count > 0)
clientCertificate = localCertificates[0];
//If user call back is registered
if (ClientCertificateSelectionCallback != null)
{
var args = new CertificateSelectionEventArgs();
args.targetHost = targetHost;
args.localCertificates = localCertificates;
args.remoteCertificate = remoteCertificate;
args.acceptableIssuers = acceptableIssuers;
args.clientCertificate = clientCertificate;
Delegate[] invocationList = ClientCertificateSelectionCallback.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, CertificateSelectionEventArgs, Task>)invocationList[i])(null, args);
}
Task.WhenAll(handlerTasks).Wait();
return args.clientCertificate;
}
return clientCertificate;
}
}
}
namespace Titanium.Web.Proxy.Compression namespace Titanium.Web.Proxy.Compression
{ {
class CompressionFactory /// <summary>
/// A factory to generate the compression methods based on the type of compression
/// </summary>
internal class CompressionFactory
{ {
public ICompression Create(string type) public ICompression Create(string type)
{ {
......
...@@ -4,7 +4,10 @@ using System.Threading.Tasks; ...@@ -4,7 +4,10 @@ using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression namespace Titanium.Web.Proxy.Compression
{ {
class DeflateCompression : ICompression /// <summary>
/// Concrete implementation of deflate compression
/// </summary>
internal class DeflateCompression : ICompression
{ {
public async Task<byte[]> Compress(byte[] responseBody) public async Task<byte[]> Compress(byte[] responseBody)
{ {
...@@ -12,7 +15,7 @@ namespace Titanium.Web.Proxy.Compression ...@@ -12,7 +15,7 @@ namespace Titanium.Web.Proxy.Compression
{ {
using (var zip = new DeflateStream(ms, CompressionMode.Compress, true)) using (var zip = new DeflateStream(ms, CompressionMode.Compress, true))
{ {
await zip.WriteAsync(responseBody, 0, responseBody.Length).ConfigureAwait(false); await zip.WriteAsync(responseBody, 0, responseBody.Length);
} }
return ms.ToArray(); return ms.ToArray();
......
...@@ -4,7 +4,10 @@ using System.Threading.Tasks; ...@@ -4,7 +4,10 @@ using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression namespace Titanium.Web.Proxy.Compression
{ {
class GZipCompression : ICompression /// <summary>
/// concreate implementation of gzip compression
/// </summary>
internal class GZipCompression : ICompression
{ {
public async Task<byte[]> Compress(byte[] responseBody) public async Task<byte[]> Compress(byte[] responseBody)
{ {
...@@ -12,7 +15,7 @@ namespace Titanium.Web.Proxy.Compression ...@@ -12,7 +15,7 @@ namespace Titanium.Web.Proxy.Compression
{ {
using (var zip = new GZipStream(ms, CompressionMode.Compress, true)) using (var zip = new GZipStream(ms, CompressionMode.Compress, true))
{ {
await zip.WriteAsync(responseBody, 0, responseBody.Length).ConfigureAwait(false); await zip.WriteAsync(responseBody, 0, responseBody.Length);
} }
return ms.ToArray(); return ms.ToArray();
......
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
namespace Titanium.Web.Proxy.Compression namespace Titanium.Web.Proxy.Compression
{ {
/// <summary>
/// An inteface for http compression
/// </summary>
interface ICompression interface ICompression
{ {
Task<byte[]> Compress(byte[] responseBody); Task<byte[]> Compress(byte[] responseBody);
......
...@@ -4,7 +4,10 @@ using System.Threading.Tasks; ...@@ -4,7 +4,10 @@ using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression namespace Titanium.Web.Proxy.Compression
{ {
class ZlibCompression : ICompression /// <summary>
/// concrete implementation of zlib compression
/// </summary>
internal class ZlibCompression : ICompression
{ {
public async Task<byte[]> Compress(byte[] responseBody) public async Task<byte[]> Compress(byte[] responseBody)
{ {
...@@ -12,7 +15,7 @@ namespace Titanium.Web.Proxy.Compression ...@@ -12,7 +15,7 @@ namespace Titanium.Web.Proxy.Compression
{ {
using (var zip = new ZlibStream(ms, CompressionMode.Compress, true)) using (var zip = new ZlibStream(ms, CompressionMode.Compress, true))
{ {
await zip.WriteAsync(responseBody, 0, responseBody.Length).ConfigureAwait(false); await zip.WriteAsync(responseBody, 0, responseBody.Length);
} }
return ms.ToArray(); return ms.ToArray();
......
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
/// <summary>
/// A factory to generate the de-compression methods based on the type of compression
/// </summary>
internal class DecompressionFactory internal class DecompressionFactory
{ {
internal IDecompression Create(string type) internal IDecompression Create(string type)
......
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
/// <summary>
/// When no compression is specified just return the byte array
/// </summary>
internal class DefaultDecompression : IDecompression internal class DefaultDecompression : IDecompression
{ {
public Task<byte[]> Decompress(byte[] compressedArray) public Task<byte[]> Decompress(byte[] compressedArray)
......
...@@ -5,6 +5,9 @@ using Titanium.Web.Proxy.Shared; ...@@ -5,6 +5,9 @@ using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
/// <summary>
/// concrete implementation of deflate de-compression
/// </summary>
internal class DeflateDecompression : IDecompression internal class DeflateDecompression : IDecompression
{ {
public async Task<byte[]> Decompress(byte[] compressedArray) public async Task<byte[]> Decompress(byte[] compressedArray)
...@@ -18,9 +21,9 @@ namespace Titanium.Web.Proxy.Decompression ...@@ -18,9 +21,9 @@ namespace Titanium.Web.Proxy.Decompression
using (var output = new MemoryStream()) using (var output = new MemoryStream())
{ {
int read; int read;
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0) while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
{ {
await output.WriteAsync(buffer, 0, read).ConfigureAwait(false); await output.WriteAsync(buffer, 0, read);
} }
return output.ToArray(); return output.ToArray();
......
...@@ -5,6 +5,9 @@ using Titanium.Web.Proxy.Shared; ...@@ -5,6 +5,9 @@ using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
/// <summary>
/// concrete implementation of gzip de-compression
/// </summary>
internal class GZipDecompression : IDecompression internal class GZipDecompression : IDecompression
{ {
public async Task<byte[]> Decompress(byte[] compressedArray) public async Task<byte[]> Decompress(byte[] compressedArray)
...@@ -15,9 +18,9 @@ namespace Titanium.Web.Proxy.Decompression ...@@ -15,9 +18,9 @@ namespace Titanium.Web.Proxy.Decompression
using (var output = new MemoryStream()) using (var output = new MemoryStream())
{ {
int read; int read;
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0) while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
{ {
await output.WriteAsync(buffer, 0, read).ConfigureAwait(false); await output.WriteAsync(buffer, 0, read);
} }
return output.ToArray(); return output.ToArray();
} }
......
using System.IO; using System.Threading.Tasks;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
/// <summary>
/// An interface for decompression
/// </summary>
internal interface IDecompression internal interface IDecompression
{ {
Task<byte[]> Decompress(byte[] compressedArray); Task<byte[]> Decompress(byte[] compressedArray);
......
...@@ -5,6 +5,9 @@ using Titanium.Web.Proxy.Shared; ...@@ -5,6 +5,9 @@ using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
/// <summary>
/// concrete implemetation of zlib de-compression
/// </summary>
internal class ZlibDecompression : IDecompression internal class ZlibDecompression : IDecompression
{ {
public async Task<byte[]> Decompress(byte[] compressedArray) public async Task<byte[]> Decompress(byte[] compressedArray)
...@@ -17,9 +20,9 @@ namespace Titanium.Web.Proxy.Decompression ...@@ -17,9 +20,9 @@ namespace Titanium.Web.Proxy.Decompression
using (var output = new MemoryStream()) using (var output = new MemoryStream())
{ {
int read; int read;
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0) while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
{ {
await output.WriteAsync(buffer, 0, read).ConfigureAwait(false); await output.WriteAsync(buffer, 0, read);
} }
return output.ToArray(); return output.ToArray();
} }
......
...@@ -3,6 +3,9 @@ using System.Security.Cryptography.X509Certificates; ...@@ -3,6 +3,9 @@ using System.Security.Cryptography.X509Certificates;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
/// <summary>
/// An argument passed on to user for client certificate selection during mutual SSL authentication
/// </summary>
public class CertificateSelectionEventArgs : EventArgs, IDisposable public class CertificateSelectionEventArgs : EventArgs, IDisposable
{ {
public object sender { get; internal set; } public object sender { get; internal set; }
......
...@@ -4,6 +4,9 @@ using System.Security.Cryptography.X509Certificates; ...@@ -4,6 +4,9 @@ using System.Security.Cryptography.X509Certificates;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
/// <summary>
/// An argument passed on to the user for validating the server certificate during SSL authentication
/// </summary>
public class CertificateValidationEventArgs : EventArgs, IDisposable public class CertificateValidationEventArgs : EventArgs, IDisposable
{ {
public X509Certificate Certificate { get; internal set; } public X509Certificate Certificate { get; internal set; }
......
...@@ -9,7 +9,6 @@ using Titanium.Web.Proxy.Extensions; ...@@ -9,7 +9,6 @@ using Titanium.Web.Proxy.Extensions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Network;
using System.Net; using System.Net;
using System.Net.Sockets;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
...@@ -81,7 +80,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -81,7 +80,7 @@ namespace Titanium.Web.Proxy.EventArguments
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol //For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if (WebSession.Request.IsChunked) if (WebSession.Request.IsChunked)
{ {
await this.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(requestBodyStream).ConfigureAwait(false); await this.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(requestBodyStream);
} }
else else
{ {
...@@ -89,13 +88,13 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -89,13 +88,13 @@ namespace Titanium.Web.Proxy.EventArguments
if (WebSession.Request.ContentLength > 0) if (WebSession.Request.ContentLength > 0)
{ {
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response //If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await this.ProxyClient.ClientStreamReader.CopyBytesToStream(requestBodyStream, WebSession.Request.ContentLength).ConfigureAwait(false); await this.ProxyClient.ClientStreamReader.CopyBytesToStream(requestBodyStream, WebSession.Request.ContentLength);
} }
else if(WebSession.Request.HttpVersion.Major == 1 && WebSession.Request.HttpVersion.Minor == 0) else if(WebSession.Request.HttpVersion.Major == 1 && WebSession.Request.HttpVersion.Minor == 0)
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(requestBodyStream, long.MaxValue).ConfigureAwait(false); await WebSession.ServerConnection.StreamReader.CopyBytesToStream(requestBodyStream, long.MaxValue);
} }
WebSession.Request.RequestBody = await GetDecompressedResponseBody(WebSession.Request.ContentEncoding, requestBodyStream.ToArray()).ConfigureAwait(false); WebSession.Request.RequestBody = await GetDecompressedResponseBody(WebSession.Request.ContentEncoding, requestBodyStream.ToArray());
} }
//Now set the flag to true //Now set the flag to true
...@@ -118,21 +117,21 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -118,21 +117,21 @@ namespace Titanium.Web.Proxy.EventArguments
//If chuncked the read chunk by chunk until we hit chunk end symbol //If chuncked the read chunk by chunk until we hit chunk end symbol
if (WebSession.Response.IsChunked) if (WebSession.Response.IsChunked)
{ {
await WebSession.ServerConnection.StreamReader.CopyBytesToStreamChunked(responseBodyStream).ConfigureAwait(false); await WebSession.ServerConnection.StreamReader.CopyBytesToStreamChunked(responseBodyStream);
} }
else else
{ {
if (WebSession.Response.ContentLength > 0) if (WebSession.Response.ContentLength > 0)
{ {
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response //If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream, WebSession.Response.ContentLength).ConfigureAwait(false); await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream, WebSession.Response.ContentLength);
} }
else if(WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0) else if(WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0)
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream, long.MaxValue).ConfigureAwait(false); await WebSession.ServerConnection.StreamReader.CopyBytesToStream(responseBodyStream, long.MaxValue);
} }
WebSession.Response.ResponseBody = await GetDecompressedResponseBody(WebSession.Response.ContentEncoding, responseBodyStream.ToArray()).ConfigureAwait(false); WebSession.Response.ResponseBody = await GetDecompressedResponseBody(WebSession.Response.ContentEncoding, responseBodyStream.ToArray());
} }
//set this to true for caching //set this to true for caching
...@@ -149,7 +148,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -149,7 +148,7 @@ namespace Titanium.Web.Proxy.EventArguments
if (WebSession.Request.RequestLocked) if (WebSession.Request.RequestLocked)
throw new Exception("You cannot call this function after request is made to server."); throw new Exception("You cannot call this function after request is made to server.");
await ReadRequestBody().ConfigureAwait(false); await ReadRequestBody();
return WebSession.Request.RequestBody; return WebSession.Request.RequestBody;
} }
/// <summary> /// <summary>
...@@ -162,7 +161,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -162,7 +161,7 @@ namespace Titanium.Web.Proxy.EventArguments
throw new Exception("You cannot call this function after request is made to server."); throw new Exception("You cannot call this function after request is made to server.");
await ReadRequestBody().ConfigureAwait(false); await ReadRequestBody();
//Use the encoding specified in request to decode the byte[] data to string //Use the encoding specified in request to decode the byte[] data to string
return WebSession.Request.RequestBodyString ?? (WebSession.Request.RequestBodyString = WebSession.Request.Encoding.GetString(WebSession.Request.RequestBody)); return WebSession.Request.RequestBodyString ?? (WebSession.Request.RequestBodyString = WebSession.Request.Encoding.GetString(WebSession.Request.RequestBody));
...@@ -180,7 +179,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -180,7 +179,7 @@ namespace Titanium.Web.Proxy.EventArguments
//syphon out the request body from client before setting the new body //syphon out the request body from client before setting the new body
if (!WebSession.Request.RequestBodyRead) if (!WebSession.Request.RequestBodyRead)
{ {
await ReadRequestBody().ConfigureAwait(false); await ReadRequestBody();
} }
WebSession.Request.RequestBody = body; WebSession.Request.RequestBody = body;
...@@ -203,7 +202,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -203,7 +202,7 @@ namespace Titanium.Web.Proxy.EventArguments
//syphon out the request body from client before setting the new body //syphon out the request body from client before setting the new body
if (!WebSession.Request.RequestBodyRead) if (!WebSession.Request.RequestBodyRead)
{ {
await ReadRequestBody().ConfigureAwait(false); await ReadRequestBody();
} }
await SetRequestBody(WebSession.Request.Encoding.GetBytes(body)); await SetRequestBody(WebSession.Request.Encoding.GetBytes(body));
...@@ -219,7 +218,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -219,7 +218,7 @@ namespace Titanium.Web.Proxy.EventArguments
if (!WebSession.Request.RequestLocked) if (!WebSession.Request.RequestLocked)
throw new Exception("You cannot call this function before request is made to server."); throw new Exception("You cannot call this function before request is made to server.");
await ReadResponseBody().ConfigureAwait(false); await ReadResponseBody();
return WebSession.Response.ResponseBody; return WebSession.Response.ResponseBody;
} }
...@@ -232,7 +231,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -232,7 +231,7 @@ namespace Titanium.Web.Proxy.EventArguments
if (!WebSession.Request.RequestLocked) if (!WebSession.Request.RequestLocked)
throw new Exception("You cannot call this function before request is made to server."); throw new Exception("You cannot call this function before request is made to server.");
await GetResponseBody().ConfigureAwait(false); await GetResponseBody();
return WebSession.Response.ResponseBodyString ?? (WebSession.Response.ResponseBodyString = WebSession.Response.Encoding.GetString(WebSession.Response.ResponseBody)); return WebSession.Response.ResponseBodyString ?? (WebSession.Response.ResponseBodyString = WebSession.Response.Encoding.GetString(WebSession.Response.ResponseBody));
} }
...@@ -249,7 +248,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -249,7 +248,7 @@ namespace Titanium.Web.Proxy.EventArguments
//syphon out the response body from server before setting the new body //syphon out the response body from server before setting the new body
if (WebSession.Response.ResponseBody == null) if (WebSession.Response.ResponseBody == null)
{ {
await GetResponseBody().ConfigureAwait(false); await GetResponseBody();
} }
WebSession.Response.ResponseBody = body; WebSession.Response.ResponseBody = body;
...@@ -273,12 +272,12 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -273,12 +272,12 @@ namespace Titanium.Web.Proxy.EventArguments
//syphon out the response body from server before setting the new body //syphon out the response body from server before setting the new body
if (WebSession.Response.ResponseBody == null) if (WebSession.Response.ResponseBody == null)
{ {
await GetResponseBody().ConfigureAwait(false); await GetResponseBody();
} }
var bodyBytes = WebSession.Response.Encoding.GetBytes(body); var bodyBytes = WebSession.Response.Encoding.GetBytes(body);
await SetResponseBody(bodyBytes).ConfigureAwait(false); await SetResponseBody(bodyBytes);
} }
private async Task<byte[]> GetDecompressedResponseBody(string encodingType, byte[] responseBodyStream) private async Task<byte[]> GetDecompressedResponseBody(string encodingType, byte[] responseBodyStream)
...@@ -286,7 +285,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -286,7 +285,7 @@ namespace Titanium.Web.Proxy.EventArguments
var decompressionFactory = new DecompressionFactory(); var decompressionFactory = new DecompressionFactory();
var decompressor = decompressionFactory.Create(encodingType); var decompressor = decompressionFactory.Create(encodingType);
return await decompressor.Decompress(responseBodyStream).ConfigureAwait(false); return await decompressor.Decompress(responseBodyStream);
} }
...@@ -306,7 +305,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -306,7 +305,7 @@ namespace Titanium.Web.Proxy.EventArguments
var result = Encoding.Default.GetBytes(html); var result = Encoding.Default.GetBytes(html);
await Ok(result).ConfigureAwait(false); await Ok(result);
} }
/// <summary> /// <summary>
...@@ -322,7 +321,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -322,7 +321,7 @@ namespace Titanium.Web.Proxy.EventArguments
response.HttpVersion = WebSession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseBody = result; response.ResponseBody = result;
await Respond(response).ConfigureAwait(false); await Respond(response);
WebSession.Request.CancelRequest = true; WebSession.Request.CancelRequest = true;
} }
...@@ -335,7 +334,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -335,7 +334,7 @@ namespace Titanium.Web.Proxy.EventArguments
response.ResponseHeaders.Add(new Models.HttpHeader("Location", url)); response.ResponseHeaders.Add(new Models.HttpHeader("Location", url));
response.ResponseBody = Encoding.ASCII.GetBytes(string.Empty); response.ResponseBody = Encoding.ASCII.GetBytes(string.Empty);
await Respond(response).ConfigureAwait(false); await Respond(response);
WebSession.Request.CancelRequest = true; WebSession.Request.CancelRequest = true;
} }
...@@ -350,7 +349,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -350,7 +349,7 @@ namespace Titanium.Web.Proxy.EventArguments
WebSession.Response = response; WebSession.Response = response;
await ProxyServer.HandleHttpSessionResponse(this).ConfigureAwait(false); await ProxyServer.HandleHttpSessionResponse(this);
} }
} }
......
using System;
namespace Titanium.Web.Proxy.Extensions
{
public static class ByteArrayExtensions
{
/// <summary>
/// Get the sub array from byte of data
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="data"></param>
/// <param name="index"></param>
/// <param name="length"></param>
/// <returns></returns>
public static T[] SubArray<T>(this T[] data, int index, int length)
{
T[] result = new T[length];
Array.Copy(data, index, result, 0, length);
return result;
}
}
}
...@@ -9,7 +9,11 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -9,7 +9,11 @@ namespace Titanium.Web.Proxy.Extensions
/// </summary> /// </summary>
internal static class HttpWebRequestExtensions internal static class HttpWebRequestExtensions
{ {
//Get encoding of the HTTP request /// <summary>
/// parse the character encoding of request from request headers
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
internal static Encoding GetEncoding(this Request request) internal static Encoding GetEncoding(this Request request)
{ {
try try
......
...@@ -6,6 +6,11 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -6,6 +6,11 @@ namespace Titanium.Web.Proxy.Extensions
{ {
internal static class HttpWebResponseExtensions internal static class HttpWebResponseExtensions
{ {
/// <summary>
/// Gets the character encoding of response from response headers
/// </summary>
/// <param name="response"></param>
/// <returns></returns>
internal static Encoding GetResponseCharacterEncoding(this Response response) internal static Encoding GetResponseCharacterEncoding(this Response response)
{ {
try try
......
using System; using System.Globalization;
using System.Globalization;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
...@@ -8,8 +7,18 @@ using Titanium.Web.Proxy.Shared; ...@@ -8,8 +7,18 @@ using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
internal static class StreamHelper /// <summary>
/// Extensions used for Stream and CustomBinaryReader objects
/// </summary>
internal static class StreamExtensions
{ {
/// <summary>
/// Copy streams asynchronously with an initial data inserted to the beginning of stream
/// </summary>
/// <param name="input"></param>
/// <param name="initialData"></param>
/// <param name="output"></param>
/// <returns></returns>
internal static async Task CopyToAsync(this Stream input, string initialData, Stream output) internal static async Task CopyToAsync(this Stream input, string initialData, Stream output)
{ {
if (!string.IsNullOrEmpty(initialData)) if (!string.IsNullOrEmpty(initialData))
...@@ -19,7 +28,13 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -19,7 +28,13 @@ namespace Titanium.Web.Proxy.Extensions
} }
await input.CopyToAsync(output); await input.CopyToAsync(output);
} }
/// <summary>
/// copies the specified bytes to the stream from the input stream
/// </summary>
/// <param name="streamReader"></param>
/// <param name="stream"></param>
/// <param name="totalBytesToRead"></param>
/// <returns></returns>
internal static async Task CopyBytesToStream(this CustomBinaryReader streamReader, Stream stream, long totalBytesToRead) internal static async Task CopyBytesToStream(this CustomBinaryReader streamReader, Stream stream, long totalBytesToRead)
{ {
var totalbytesRead = 0; var totalbytesRead = 0;
...@@ -50,11 +65,18 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -50,11 +65,18 @@ namespace Titanium.Web.Proxy.Extensions
await stream.WriteAsync(buffer, 0, buffer.Length); await stream.WriteAsync(buffer, 0, buffer.Length);
} }
} }
/// <summary>
/// Copies the stream chunked
/// </summary>
/// <param name="clientStreamReader"></param>
/// <param name="stream"></param>
/// <returns></returns>
internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, Stream stream) internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, Stream stream)
{ {
while (true) while (true)
{ {
var chuchkHead = await clientStreamReader.ReadLineAsync().ConfigureAwait(false); var chuchkHead = await clientStreamReader.ReadLineAsync();
var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber); var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);
if (chunkSize != 0) if (chunkSize != 0)
...@@ -62,14 +84,127 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -62,14 +84,127 @@ namespace Titanium.Web.Proxy.Extensions
var buffer = await clientStreamReader.ReadBytesAsync(chunkSize); var buffer = await clientStreamReader.ReadBytesAsync(chunkSize);
await stream.WriteAsync(buffer, 0, buffer.Length); await stream.WriteAsync(buffer, 0, buffer.Length);
//chunk trail //chunk trail
await clientStreamReader.ReadLineAsync().ConfigureAwait(false); await clientStreamReader.ReadLineAsync();
} }
else else
{ {
await clientStreamReader.ReadLineAsync().ConfigureAwait(false); await clientStreamReader.ReadLineAsync();
break; break;
} }
} }
} }
/// <summary>
/// Writes the byte array body to the given stream; optionally chunked
/// </summary>
/// <param name="clientStream"></param>
/// <param name="data"></param>
/// <param name="isChunked"></param>
/// <returns></returns>
internal static async Task WriteResponseBody(this Stream clientStream, byte[] data, bool isChunked)
{
if (!isChunked)
{
await clientStream.WriteAsync(data, 0, data.Length);
}
else
await WriteResponseBodyChunked(data, clientStream);
}
/// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// optionally chunked
/// </summary>
/// <param name="inStreamReader"></param>
/// <param name="outStream"></param>
/// <param name="isChunked"></param>
/// <param name="ContentLength"></param>
/// <returns></returns>
internal static async Task WriteResponseBody(this CustomBinaryReader inStreamReader, Stream outStream, bool isChunked, long ContentLength)
{
if (!isChunked)
{
//http 1.0
if (ContentLength == -1)
ContentLength = long.MaxValue;
int bytesToRead = ProxyConstants.BUFFER_SIZE;
if (ContentLength < ProxyConstants.BUFFER_SIZE)
bytesToRead = (int)ContentLength;
var buffer = new byte[ProxyConstants.BUFFER_SIZE];
var bytesRead = 0;
var totalBytesRead = 0;
while ((bytesRead += await inStreamReader.BaseStream.ReadAsync(buffer, 0, bytesToRead)) > 0)
{
await outStream.WriteAsync(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
if (totalBytesRead == ContentLength)
break;
bytesRead = 0;
var remainingBytes = (ContentLength - totalBytesRead);
bytesToRead = remainingBytes > (long)ProxyConstants.BUFFER_SIZE ? ProxyConstants.BUFFER_SIZE : (int)remainingBytes;
}
}
else
await WriteResponseBodyChunked(inStreamReader, outStream);
}
/// <summary>
/// Copies the streams chunked
/// </summary>
/// <param name="inStreamReader"></param>
/// <param name="outStream"></param>
/// <returns></returns>
internal static async Task WriteResponseBodyChunked(this CustomBinaryReader inStreamReader, Stream outStream)
{
while (true)
{
var chunkHead = await inStreamReader.ReadLineAsync();
var chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
if (chunkSize != 0)
{
var buffer = await inStreamReader.ReadBytesAsync(chunkSize);
var chunkHeadBytes = Encoding.ASCII.GetBytes(chunkSize.ToString("x2"));
await outStream.WriteAsync(chunkHeadBytes, 0, chunkHeadBytes.Length);
await outStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await outStream.WriteAsync(buffer, 0, chunkSize);
await outStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await inStreamReader.ReadLineAsync();
}
else
{
await inStreamReader.ReadLineAsync();
await outStream.WriteAsync(ProxyConstants.ChunkEnd, 0, ProxyConstants.ChunkEnd.Length);
break;
}
}
}
/// <summary>
/// Copies the given input bytes to output stream chunked
/// </summary>
/// <param name="data"></param>
/// <param name="outStream"></param>
/// <returns></returns>
internal static async Task WriteResponseBodyChunked(this byte[] data, Stream outStream)
{
var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2"));
await outStream.WriteAsync(chunkHead, 0, chunkHead.Length);
await outStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await outStream.WriteAsync(data, 0, data.Length);
await outStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);
await outStream.WriteAsync(ProxyConstants.ChunkEnd, 0, ProxyConstants.ChunkEnd.Length);
}
} }
} }
\ No newline at end of file
...@@ -4,6 +4,11 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -4,6 +4,11 @@ namespace Titanium.Web.Proxy.Extensions
{ {
internal static class TcpExtensions internal static class TcpExtensions
{ {
/// <summary>
/// verifies if the underlying socket is connected before using a TcpClient connection
/// </summary>
/// <param name="client"></param>
/// <returns></returns>
internal static bool IsConnected(this Socket client) internal static bool IsConnected(this Socket client)
{ {
// This is how you can determine whether a socket is still connected. // This is how you can determine whether a socket is still connected.
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net.Security;
using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
/// <summary> /// <summary>
/// A custom binary reader that would allo us to read string line by line /// A custom binary reader that would allo us to read string line by line
/// using the specified encoding /// using the specified encoding
...@@ -18,10 +17,14 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -18,10 +17,14 @@ namespace Titanium.Web.Proxy.Helpers
internal class CustomBinaryReader : IDisposable internal class CustomBinaryReader : IDisposable
{ {
private Stream stream; private Stream stream;
private Encoding encoding;
internal CustomBinaryReader(Stream stream) internal CustomBinaryReader(Stream stream)
{ {
this.stream = stream; this.stream = stream;
//default to UTF-8
this.encoding = Encoding.UTF8;
} }
internal Stream BaseStream => stream; internal Stream BaseStream => stream;
...@@ -32,8 +35,8 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -32,8 +35,8 @@ namespace Titanium.Web.Proxy.Helpers
/// <returns></returns> /// <returns></returns>
internal async Task<string> ReadLineAsync() internal async Task<string> ReadLineAsync()
{ {
var readBuffer = new StringBuilder(); using (var readBuffer = new MemoryStream())
{
try try
{ {
var lastChar = default(char); var lastChar = default(char);
...@@ -41,23 +44,30 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -41,23 +44,30 @@ namespace Titanium.Web.Proxy.Helpers
while ((await this.stream.ReadAsync(buffer, 0, 1)) > 0) while ((await this.stream.ReadAsync(buffer, 0, 1)) > 0)
{ {
//if new line
if (lastChar == '\r' && buffer[0] == '\n') if (lastChar == '\r' && buffer[0] == '\n')
{ {
return await Task.FromResult(readBuffer.Remove(readBuffer.Length - 1, 1).ToString()); var result = readBuffer.ToArray();
return encoding.GetString(result.SubArray(0, result.Length - 1));
} }
//end of stream
if (buffer[0] == '\0') if (buffer[0] == '\0')
{ {
return await Task.FromResult(readBuffer.ToString()); return encoding.GetString(readBuffer.ToArray());
} }
readBuffer.Append((char)buffer[0]);
await readBuffer.WriteAsync(buffer,0,1);
//store last char for new line comparison
lastChar = (char)buffer[0]; lastChar = (char)buffer[0];
} }
return await Task.FromResult(readBuffer.ToString()); return encoding.GetString(readBuffer.ToArray());
} }
catch (IOException) catch (IOException)
{ {
return await Task.FromResult(readBuffer.ToString()); throw;
}
} }
} }
...@@ -69,13 +79,18 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -69,13 +79,18 @@ namespace Titanium.Web.Proxy.Helpers
{ {
string tmpLine; string tmpLine;
var requestLines = new List<string>(); var requestLines = new List<string>();
while (!string.IsNullOrEmpty(tmpLine = await ReadLineAsync().ConfigureAwait(false))) while (!string.IsNullOrEmpty(tmpLine = await ReadLineAsync()))
{ {
requestLines.Add(tmpLine); requestLines.Add(tmpLine);
} }
return requestLines; return requestLines;
} }
/// <summary>
/// Read the specified number of raw bytes from the base stream
/// </summary>
/// <param name="totalBytesToRead"></param>
/// <returns></returns>
internal async Task<byte[]> ReadBytesAsync(long totalBytesToRead) internal async Task<byte[]> ReadBytesAsync(long totalBytesToRead)
{ {
int bytesToRead = ProxyConstants.BUFFER_SIZE; int bytesToRead = ProxyConstants.BUFFER_SIZE;
...@@ -90,9 +105,9 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -90,9 +105,9 @@ namespace Titanium.Web.Proxy.Helpers
using (var outStream = new MemoryStream()) using (var outStream = new MemoryStream())
{ {
while ((bytesRead += await this.stream.ReadAsync(buffer, 0, bytesToRead).ConfigureAwait(false)) > 0) while ((bytesRead += await this.stream.ReadAsync(buffer, 0, bytesToRead)) > 0)
{ {
await outStream.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false); await outStream.WriteAsync(buffer, 0, bytesRead);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
if (totalBytesRead == totalBytesToRead) if (totalBytesRead == totalBytesToRead)
......
...@@ -5,8 +5,12 @@ using System.Text.RegularExpressions; ...@@ -5,8 +5,12 @@ using System.Text.RegularExpressions;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
/// <summary>
/// Helper classes for setting system proxy settings
/// </summary>
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
internal static class NativeMethods internal static class NativeMethods
{ {
[DllImport("wininet.dll")] [DllImport("wininet.dll")]
...@@ -59,14 +63,16 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -59,14 +63,16 @@ namespace Titanium.Web.Proxy.Helpers
Refresh(); Refresh();
} }
/// <summary>
/// Remove the http proxy setting from current machine
/// </summary>
internal static void RemoveHttpProxy() internal static void RemoveHttpProxy()
{ {
var reg = Registry.CurrentUser.OpenSubKey( var reg = Registry.CurrentUser.OpenSubKey(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true); "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true);
if (reg != null) if (reg != null)
{ {
if (reg.GetValue("ProxyServer")!=null) if (reg.GetValue("ProxyServer") != null)
{ {
var exisitingContent = reg.GetValue("ProxyServer") as string; var exisitingContent = reg.GetValue("ProxyServer") as string;
...@@ -89,6 +95,11 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -89,6 +95,11 @@ namespace Titanium.Web.Proxy.Helpers
Refresh(); Refresh();
} }
/// <summary>
/// Set the HTTPS proxy server for current machine
/// </summary>
/// <param name="hostname"></param>
/// <param name="port"></param>
internal static void SetHttpsProxy(string hostname, int port) internal static void SetHttpsProxy(string hostname, int port)
{ {
var reg = Registry.CurrentUser.OpenSubKey( var reg = Registry.CurrentUser.OpenSubKey(
...@@ -116,6 +127,9 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -116,6 +127,9 @@ namespace Titanium.Web.Proxy.Helpers
Refresh(); Refresh();
} }
/// <summary>
/// Removes the https proxy setting to nothing
/// </summary>
internal static void RemoveHttpsProxy() internal static void RemoveHttpsProxy()
{ {
var reg = Registry.CurrentUser.OpenSubKey( var reg = Registry.CurrentUser.OpenSubKey(
...@@ -146,6 +160,9 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -146,6 +160,9 @@ namespace Titanium.Web.Proxy.Helpers
Refresh(); Refresh();
} }
/// <summary>
/// Removes all types of proxy settings (both http & https)
/// </summary>
internal static void DisableAllProxy() internal static void DisableAllProxy()
{ {
var reg = Registry.CurrentUser.OpenSubKey( var reg = Registry.CurrentUser.OpenSubKey(
...@@ -159,6 +176,12 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -159,6 +176,12 @@ namespace Titanium.Web.Proxy.Helpers
Refresh(); Refresh();
} }
/// <summary>
/// Get the current system proxy setting values
/// </summary>
/// <param name="prevServerValue"></param>
/// <returns></returns>
private static List<HttpSystemProxyValue> GetSystemProxyValues(string prevServerValue) private static List<HttpSystemProxyValue> GetSystemProxyValues(string prevServerValue)
{ {
var result = new List<HttpSystemProxyValue>(); var result = new List<HttpSystemProxyValue>();
...@@ -187,6 +210,11 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -187,6 +210,11 @@ namespace Titanium.Web.Proxy.Helpers
return result; return result;
} }
/// <summary>
/// Parses the system proxy setting string
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private static HttpSystemProxyValue parseProxyValue(string value) private static HttpSystemProxyValue parseProxyValue(string value)
{ {
var tmp = Regex.Replace(value, @"\s+", " ").Trim().ToLower(); var tmp = Regex.Replace(value, @"\s+", " ").Trim().ToLower();
...@@ -213,7 +241,10 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -213,7 +241,10 @@ namespace Titanium.Web.Proxy.Helpers
return null; return null;
} }
/// <summary>
/// Prepares the proxy server registry (create empty values if they don't exist)
/// </summary>
/// <param name="reg"></param>
private static void prepareRegistry(RegistryKey reg) private static void prepareRegistry(RegistryKey reg)
{ {
if (reg.GetValue("ProxyEnable") == null) if (reg.GetValue("ProxyEnable") == null)
...@@ -228,7 +259,9 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -228,7 +259,9 @@ namespace Titanium.Web.Proxy.Helpers
} }
/// <summary>
/// Refresh the settings so that the system know about a change in proxy setting
/// </summary>
private static void Refresh() private static void Refresh()
{ {
NativeMethods.InternetSetOption(IntPtr.Zero, InternetOptionSettingsChanged, IntPtr.Zero, 0); NativeMethods.InternetSetOption(IntPtr.Zero, InternetOptionSettingsChanged, IntPtr.Zero, 0);
......
...@@ -12,11 +12,24 @@ using Titanium.Web.Proxy.Shared; ...@@ -12,11 +12,24 @@ using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
internal class TcpHelper internal class TcpHelper
{ {
/// <summary>
/// relays the input clientStream to the server at the specified host name & port with the given httpCmd & headers as prefix
/// Usefull for websocket requests
/// </summary>
/// <param name="clientStream"></param>
/// <param name="httpCmd"></param>
/// <param name="requestHeaders"></param>
/// <param name="hostName"></param>
/// <param name="tunnelPort"></param>
/// <param name="isHttps"></param>
/// <returns></returns>
internal async static Task SendRaw(Stream clientStream, string httpCmd, List<HttpHeader> requestHeaders, string hostName, internal async static Task SendRaw(Stream clientStream, string httpCmd, List<HttpHeader> requestHeaders, string hostName,
int tunnelPort, bool isHttps) int tunnelPort, bool isHttps)
{ {
//prepare the prefix content
StringBuilder sb = null; StringBuilder sb = null;
if (httpCmd != null || requestHeaders != null) if (httpCmd != null || requestHeaders != null)
{ {
...@@ -38,7 +51,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -38,7 +51,7 @@ namespace Titanium.Web.Proxy.Helpers
TcpClient tunnelClient = null; TcpClient tunnelClient = null;
Stream tunnelStream = null; Stream tunnelStream = null;
//create the TcpClient to the server
try try
{ {
tunnelClient = new TcpClient(hostName, tunnelPort); tunnelClient = new TcpClient(hostName, tunnelPort);
...@@ -64,6 +77,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -64,6 +77,7 @@ namespace Titanium.Web.Proxy.Helpers
Task sendRelay; Task sendRelay;
//Now async relay all server=>client & client=>server data
if (sb != null) if (sb != null)
sendRelay = clientStream.CopyToAsync(sb.ToString(), tunnelStream); sendRelay = clientStream.CopyToAsync(sb.ToString(), tunnelStream);
else else
...@@ -72,7 +86,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -72,7 +86,7 @@ namespace Titanium.Web.Proxy.Helpers
var receiveRelay = tunnelStream.CopyToAsync(string.Empty, clientStream); var receiveRelay = tunnelStream.CopyToAsync(string.Empty, clientStream);
await Task.WhenAll(sendRelay, receiveRelay).ConfigureAwait(false); await Task.WhenAll(sendRelay, receiveRelay);
} }
catch catch
{ {
......
...@@ -9,6 +9,9 @@ using Titanium.Web.Proxy.Shared; ...@@ -9,6 +9,9 @@ using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
/// <summary>
/// Used to communicate with the server over HTTP(S)
/// </summary>
public class HttpWebClient public class HttpWebClient
{ {
/// <summary> /// <summary>
...@@ -19,6 +22,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -19,6 +22,9 @@ namespace Titanium.Web.Proxy.Http
public Request Request { get; set; } public Request Request { get; set; }
public Response Response { get; set; } public Response Response { get; set; }
/// <summary>
/// Is Https?
/// </summary>
public bool IsHttps public bool IsHttps
{ {
get get
...@@ -27,6 +33,10 @@ namespace Titanium.Web.Proxy.Http ...@@ -27,6 +33,10 @@ namespace Titanium.Web.Proxy.Http
} }
} }
/// <summary>
/// Set the tcp connection to server used by this webclient
/// </summary>
/// <param name="Connection"></param>
internal void SetConnection(TcpConnection Connection) internal void SetConnection(TcpConnection Connection)
{ {
Connection.LastAccess = DateTime.Now; Connection.LastAccess = DateTime.Now;
...@@ -39,19 +49,24 @@ namespace Titanium.Web.Proxy.Http ...@@ -39,19 +49,24 @@ namespace Titanium.Web.Proxy.Http
this.Response = new Response(); this.Response = new Response();
} }
/// <summary>
/// Prepare & send the http(s) request
/// </summary>
/// <returns></returns>
internal async Task SendRequest() internal async Task SendRequest()
{ {
Stream stream = ServerConnection.Stream; Stream stream = ServerConnection.Stream;
StringBuilder requestLines = new StringBuilder(); StringBuilder requestLines = new StringBuilder();
//prepare the request & headers
requestLines.AppendLine(string.Join(" ", new string[3] requestLines.AppendLine(string.Join(" ", new string[3]
{ {
this.Request.Method, this.Request.Method,
this.Request.RequestUri.PathAndQuery, this.Request.RequestUri.PathAndQuery,
string.Format("HTTP/{0}.{1}",this.Request.HttpVersion.Major, this.Request.HttpVersion.Minor) string.Format("HTTP/{0}.{1}",this.Request.HttpVersion.Major, this.Request.HttpVersion.Minor)
})); }));
//write request headers
foreach (HttpHeader httpHeader in this.Request.RequestHeaders) foreach (HttpHeader httpHeader in this.Request.RequestHeaders)
{ {
requestLines.AppendLine(httpHeader.Name + ':' + httpHeader.Value); requestLines.AppendLine(httpHeader.Name + ':' + httpHeader.Value);
...@@ -76,17 +91,21 @@ namespace Titanium.Web.Proxy.Http ...@@ -76,17 +91,21 @@ namespace Titanium.Web.Proxy.Http
&& responseStatusDescription.ToLower().Equals("continue")) && responseStatusDescription.ToLower().Equals("continue"))
{ {
this.Request.Is100Continue = true; this.Request.Is100Continue = true;
await ServerConnection.StreamReader.ReadLineAsync().ConfigureAwait(false); await ServerConnection.StreamReader.ReadLineAsync();
} }
else if (responseStatusCode.Equals("417") else if (responseStatusCode.Equals("417")
&& responseStatusDescription.ToLower().Equals("expectation failed")) && responseStatusDescription.ToLower().Equals("expectation failed"))
{ {
this.Request.ExpectationFailed = true; this.Request.ExpectationFailed = true;
await ServerConnection.StreamReader.ReadLineAsync().ConfigureAwait(false); await ServerConnection.StreamReader.ReadLineAsync();
} }
} }
} }
/// <summary>
/// Receive & parse the http response from server
/// </summary>
/// <returns></returns>
internal async Task ReceiveResponse() internal async Task ReceiveResponse()
{ {
//return if this is already read //return if this is already read
...@@ -96,7 +115,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -96,7 +115,7 @@ namespace Titanium.Web.Proxy.Http
if (string.IsNullOrEmpty(httpResult[0])) if (string.IsNullOrEmpty(httpResult[0]))
{ {
await ServerConnection.StreamReader.ReadLineAsync().ConfigureAwait(false); await ServerConnection.StreamReader.ReadLineAsync();
} }
var httpVersion = httpResult[0].Trim().ToLower(); var httpVersion = httpResult[0].Trim().ToLower();
...@@ -116,7 +135,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -116,7 +135,7 @@ namespace Titanium.Web.Proxy.Http
{ {
this.Response.Is100Continue = true; this.Response.Is100Continue = true;
this.Response.ResponseStatusCode = null; this.Response.ResponseStatusCode = null;
await ServerConnection.StreamReader.ReadLineAsync().ConfigureAwait(false); await ServerConnection.StreamReader.ReadLineAsync();
await ReceiveResponse(); await ReceiveResponse();
return; return;
} }
...@@ -125,12 +144,13 @@ namespace Titanium.Web.Proxy.Http ...@@ -125,12 +144,13 @@ namespace Titanium.Web.Proxy.Http
{ {
this.Response.ExpectationFailed = true; this.Response.ExpectationFailed = true;
this.Response.ResponseStatusCode = null; this.Response.ResponseStatusCode = null;
await ServerConnection.StreamReader.ReadLineAsync().ConfigureAwait(false); await ServerConnection.StreamReader.ReadLineAsync();
await ReceiveResponse(); await ReceiveResponse();
return; return;
} }
List<string> responseLines = await ServerConnection.StreamReader.ReadAllLinesAsync().ConfigureAwait(false); //read response headers
List<string> responseLines = await ServerConnection.StreamReader.ReadAllLinesAsync();
for (int index = 0; index < responseLines.Count; ++index) for (int index = 0; index < responseLines.Count; ++index)
{ {
......
...@@ -7,12 +7,29 @@ using Titanium.Web.Proxy.Extensions; ...@@ -7,12 +7,29 @@ using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
/// <summary>
/// A HTTP(S) request object
/// </summary>
public class Request public class Request
{ {
/// <summary>
/// Request Method
/// </summary>
public string Method { get; set; } public string Method { get; set; }
/// <summary>
/// Request HTTP Uri
/// </summary>
public Uri RequestUri { get; set; } public Uri RequestUri { get; set; }
/// <summary>
/// Request Http Version
/// </summary>
public Version HttpVersion { get; set; } public Version HttpVersion { get; set; }
/// <summary>
/// Request Http hostanem
/// </summary>
internal string Host internal string Host
{ {
get get
...@@ -32,6 +49,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -32,6 +49,9 @@ namespace Titanium.Web.Proxy.Http
} }
} }
/// <summary>
/// Request content encoding
/// </summary>
internal string ContentEncoding internal string ContentEncoding
{ {
get get
...@@ -47,6 +67,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -47,6 +67,9 @@ namespace Titanium.Web.Proxy.Http
} }
} }
/// <summary>
/// Request content-length
/// </summary>
public long ContentLength public long ContentLength
{ {
get get
...@@ -65,7 +88,6 @@ namespace Titanium.Web.Proxy.Http ...@@ -65,7 +88,6 @@ namespace Titanium.Web.Proxy.Http
} }
set set
{ {
var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-length"); var header = RequestHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-length");
if (value >= 0) if (value >= 0)
...@@ -85,6 +107,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -85,6 +107,9 @@ namespace Titanium.Web.Proxy.Http
} }
} }
/// <summary>
/// Request content-type
/// </summary>
public string ContentType public string ContentType
{ {
get get
...@@ -106,6 +131,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -106,6 +131,9 @@ namespace Titanium.Web.Proxy.Http
} }
/// <summary>
/// Is request body send as chunked bytes
/// </summary>
public bool IsChunked public bool IsChunked
{ {
get get
...@@ -137,6 +165,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -137,6 +165,9 @@ namespace Titanium.Web.Proxy.Http
} }
} }
/// <summary>
/// Does this request has a 100-continue header?
/// </summary>
public bool ExpectContinue public bool ExpectContinue
{ {
get get
...@@ -147,19 +178,37 @@ namespace Titanium.Web.Proxy.Http ...@@ -147,19 +178,37 @@ namespace Titanium.Web.Proxy.Http
} }
} }
/// <summary>
/// Request Url
/// </summary>
public string Url { get { return RequestUri.OriginalString; } } public string Url { get { return RequestUri.OriginalString; } }
/// <summary>
/// Encoding for this request
/// </summary>
internal Encoding Encoding { get { return this.GetEncoding(); } } internal Encoding Encoding { get { return this.GetEncoding(); } }
/// <summary> /// <summary>
/// Terminates the underlying Tcp Connection to client after current request /// Terminates the underlying Tcp Connection to client after current request
/// </summary> /// </summary>
internal bool CancelRequest { get; set; } internal bool CancelRequest { get; set; }
/// <summary>
/// Request body as byte array
/// </summary>
internal byte[] RequestBody { get; set; } internal byte[] RequestBody { get; set; }
/// <summary>
/// request body as string
/// </summary>
internal string RequestBodyString { get; set; } internal string RequestBodyString { get; set; }
internal bool RequestBodyRead { get; set; } internal bool RequestBodyRead { get; set; }
internal bool RequestLocked { get; set; } internal bool RequestLocked { get; set; }
/// <summary>
/// Does this request has an upgrade to websocket header?
/// </summary>
internal bool UpgradeToWebSocket internal bool UpgradeToWebSocket
{ {
get get
...@@ -176,8 +225,19 @@ namespace Titanium.Web.Proxy.Http ...@@ -176,8 +225,19 @@ namespace Titanium.Web.Proxy.Http
} }
} }
/// <summary>
/// Request heade collection
/// </summary>
public List<HttpHeader> RequestHeaders { get; set; } public List<HttpHeader> RequestHeaders { get; set; }
/// <summary>
/// Does server responsed positively for 100 continue request
/// </summary>
public bool Is100Continue { get; internal set; } public bool Is100Continue { get; internal set; }
/// <summary>
/// Server responsed negatively for the request for 100 continue
/// </summary>
public bool ExpectationFailed { get; internal set; } public bool ExpectationFailed { get; internal set; }
public Request() public Request()
......
...@@ -8,7 +8,9 @@ using System; ...@@ -8,7 +8,9 @@ using System;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
/// <summary>
/// Http(s) response object
/// </summary>
public class Response public class Response
{ {
public string ResponseStatusCode { get; set; } public string ResponseStatusCode { get; set; }
...@@ -16,7 +18,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -16,7 +18,9 @@ namespace Titanium.Web.Proxy.Http
internal Encoding Encoding { get { return this.GetResponseCharacterEncoding(); } } internal Encoding Encoding { get { return this.GetResponseCharacterEncoding(); } }
/// <summary>
/// Content encoding for this response
/// </summary>
internal string ContentEncoding internal string ContentEncoding
{ {
get get
...@@ -33,6 +37,10 @@ namespace Titanium.Web.Proxy.Http ...@@ -33,6 +37,10 @@ namespace Titanium.Web.Proxy.Http
} }
internal Version HttpVersion { get; set; } internal Version HttpVersion { get; set; }
/// <summary>
/// Keep the connection alive?
/// </summary>
internal bool ResponseKeepAlive internal bool ResponseKeepAlive
{ {
get get
...@@ -49,6 +57,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -49,6 +57,9 @@ namespace Titanium.Web.Proxy.Http
} }
} }
/// <summary>
/// Content type of this response
/// </summary>
public string ContentType public string ContentType
{ {
get get
...@@ -65,6 +76,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -65,6 +76,9 @@ namespace Titanium.Web.Proxy.Http
} }
} }
/// <summary>
/// Length of response body
/// </summary>
internal long ContentLength internal long ContentLength
{ {
get get
...@@ -103,6 +117,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -103,6 +117,9 @@ namespace Titanium.Web.Proxy.Http
} }
} }
/// <summary>
/// Response transfer-encoding is chunked?
/// </summary>
internal bool IsChunked internal bool IsChunked
{ {
get get
...@@ -141,14 +158,37 @@ namespace Titanium.Web.Proxy.Http ...@@ -141,14 +158,37 @@ namespace Titanium.Web.Proxy.Http
} }
} }
/// <summary>
/// Collection of all response headers
/// </summary>
public List<HttpHeader> ResponseHeaders { get; set; } public List<HttpHeader> ResponseHeaders { get; set; }
/// <summary>
/// Response network stream
/// </summary>
internal Stream ResponseStream { get; set; } internal Stream ResponseStream { get; set; }
/// <summary>
/// response body contenst as byte array
/// </summary>
internal byte[] ResponseBody { get; set; } internal byte[] ResponseBody { get; set; }
/// <summary>
/// response body as string
/// </summary>
internal string ResponseBodyString { get; set; } internal string ResponseBodyString { get; set; }
internal bool ResponseBodyRead { get; set; } internal bool ResponseBodyRead { get; set; }
internal bool ResponseLocked { get; set; } internal bool ResponseLocked { get; set; }
/// <summary>
/// Is response 100-continue
/// </summary>
public bool Is100Continue { get; internal set; } public bool Is100Continue { get; internal set; }
/// <summary>
/// expectation failed returned by server?
/// </summary>
public bool ExpectationFailed { get; internal set; } public bool ExpectationFailed { get; internal set; }
public Response() public Response()
......
using Titanium.Web.Proxy.Network; namespace Titanium.Web.Proxy.Http.Responses
namespace Titanium.Web.Proxy.Http.Responses
{ {
/// <summary>
/// 200 Ok response
/// </summary>
public class OkResponse : Response public class OkResponse : Response
{ {
public OkResponse() public OkResponse()
......
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
namespace Titanium.Web.Proxy.Http.Responses namespace Titanium.Web.Proxy.Http.Responses
{ {
/// <summary>
/// Redirect response
/// </summary>
public class RedirectResponse : Response public class RedirectResponse : Response
{ {
public RedirectResponse() public RedirectResponse()
......
...@@ -4,6 +4,9 @@ using System.Net.Sockets; ...@@ -4,6 +4,9 @@ using System.Net.Sockets;
namespace Titanium.Web.Proxy.Models namespace Titanium.Web.Proxy.Models
{ {
/// <summary>
/// An abstract endpoint where the proxy listens
/// </summary>
public abstract class ProxyEndPoint public abstract class ProxyEndPoint
{ {
public ProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl) public ProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
...@@ -20,6 +23,10 @@ namespace Titanium.Web.Proxy.Models ...@@ -20,6 +23,10 @@ namespace Titanium.Web.Proxy.Models
internal TcpListener listener { get; set; } internal TcpListener listener { get; set; }
} }
/// <summary>
/// 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 public class ExplicitProxyEndPoint : ProxyEndPoint
{ {
internal bool IsSystemHttpProxy { get; set; } internal bool IsSystemHttpProxy { get; set; }
...@@ -34,6 +41,10 @@ namespace Titanium.Web.Proxy.Models ...@@ -34,6 +41,10 @@ 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
/// </summary>
public class TransparentProxyEndPoint : ProxyEndPoint public class TransparentProxyEndPoint : ProxyEndPoint
{ {
//Name of the Certificate need to be sent (same as the hostname we want to proxy) //Name of the Certificate need to be sent (same as the hostname we want to proxy)
......
using System; namespace Titanium.Web.Proxy.Models
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Models
{ {
/// <summary>
/// An upstream proxy this proxy uses if any
/// </summary>
public class ExternalProxy public class ExternalProxy
{ {
public string HostName { get; set; } public string HostName { get; set; }
......
using System;
using System.Security.Cryptography.X509Certificates;
namespace Titanium.Web.Proxy.Network
{
/// <summary>
/// An object that holds the cached certificate
/// </summary>
internal class CachedCertificate
{
internal X509Certificate2 Certificate { get; set; }
/// <summary>
/// last time this certificate was used
/// Usefull in determining its cache lifetime
/// </summary>
internal DateTime LastAccess { get; set; }
internal CachedCertificate()
{
LastAccess = DateTime.Now;
}
}
}
...@@ -8,20 +8,11 @@ using System.Threading.Tasks; ...@@ -8,20 +8,11 @@ using System.Threading.Tasks;
using System.Threading; using System.Threading;
using System.Linq; using System.Linq;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Network
{ {
internal class CachedCertificate /// <summary>
{ /// A class to manage SSL certificates used by this proxy server
internal X509Certificate2 Certificate { get; set; } /// </summary>
internal DateTime LastAccess { get; set; }
internal CachedCertificate()
{
LastAccess = DateTime.Now;
}
}
internal class CertificateManager : IDisposable internal class CertificateManager : IDisposable
{ {
private const string CertCreateFormat = private const string CertCreateFormat =
...@@ -85,6 +76,13 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -85,6 +76,13 @@ namespace Titanium.Web.Proxy.Helpers
return await CreateCertificate(MyStore, certificateName, isRootCertificate); return await CreateCertificate(MyStore, certificateName, isRootCertificate);
} }
/// <summary>
/// Create an SSL certificate
/// </summary>
/// <param name="store"></param>
/// <param name="certificateName"></param>
/// <param name="isRootCertificate"></param>
/// <returns></returns>
protected async virtual Task<X509Certificate2> CreateCertificate(X509Store store, string certificateName, bool isRootCertificate) protected async virtual Task<X509Certificate2> CreateCertificate(X509Store store, string certificateName, bool isRootCertificate)
{ {
await semaphoreLock.WaitAsync(); await semaphoreLock.WaitAsync();
...@@ -141,6 +139,11 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -141,6 +139,11 @@ namespace Titanium.Web.Proxy.Helpers
} }
} }
/// <summary>
/// Create certificate using makecert.exe
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
protected virtual Task<int> CreateCertificate(string[] args) protected virtual Task<int> CreateCertificate(string[] args)
{ {
...@@ -178,12 +181,24 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -178,12 +181,24 @@ namespace Titanium.Web.Proxy.Helpers
return tcs.Task; return tcs.Task;
} }
/// <summary>
/// Destroy an SSL certificate from local store
/// </summary>
/// <param name="certificateName"></param>
/// <returns></returns>
internal bool DestroyCertificate(string certificateName) internal bool DestroyCertificate(string certificateName)
{ {
return DestroyCertificate(MyStore, certificateName, false); return DestroyCertificate(MyStore, certificateName, false);
} }
/// <summary>
/// Destroy certificate from the specified store
/// optionally also remove from proxy certificate cache
/// </summary>
/// <param name="store"></param>
/// <param name="certificateName"></param>
/// <param name="removeFromCache"></param>
/// <returns></returns>
protected virtual bool DestroyCertificate(X509Store store, string certificateName, bool removeFromCache) protected virtual bool DestroyCertificate(X509Store store, string certificateName, bool removeFromCache)
{ {
X509Certificate2Collection certificates = null; X509Certificate2Collection certificates = null;
...@@ -206,7 +221,12 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -206,7 +221,12 @@ namespace Titanium.Web.Proxy.Helpers
} }
return certificates == null; return certificates == null;
} }
/// <summary>
/// Create the neccessary arguments for makecert.exe to create the required certificate
/// </summary>
/// <param name="store"></param>
/// <param name="certificateName"></param>
/// <returns></returns>
protected virtual string GetCertificateCreateArgs(X509Store store, string certificateName) protected virtual string GetCertificateCreateArgs(X509Store store, string certificateName)
{ {
bool isRootCertificate = bool isRootCertificate =
...@@ -223,11 +243,17 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -223,11 +243,17 @@ namespace Titanium.Web.Proxy.Helpers
private static bool clearCertificates { get; set; } private static bool clearCertificates { get; set; }
/// <summary>
/// Stops the certificate cache clear process
/// </summary>
internal void StopClearIdleCertificates() internal void StopClearIdleCertificates()
{ {
clearCertificates = false; clearCertificates = false;
} }
/// <summary>
/// A method to clear outdated certificates
/// </summary>
internal async void ClearIdleCertificates() internal async void ClearIdleCertificates()
{ {
clearCertificates = true; clearCertificates = true;
...@@ -248,7 +274,8 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -248,7 +274,8 @@ namespace Titanium.Web.Proxy.Helpers
} }
finally { semaphoreLock.Release(); } finally { semaphoreLock.Release(); }
await Task.Delay(1000 * 60 * 3).ConfigureAwait(false); //after a minute come back to check for outdated certificates in cache
await Task.Delay(1000 * 60);
} }
} }
......
using System;
using System.IO;
using System.Net.Sockets;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Network
{
/// <summary>
/// An object that holds TcpConnection to a particular server & port
/// </summary>
public class TcpConnection
{
internal string HostName { get; set; }
internal int port { get; set; }
internal bool IsHttps { get; set; }
/// <summary>
/// Http version
/// </summary>
internal Version Version { get; set; }
internal TcpClient TcpClient { get; set; }
/// <summary>
/// used to read lines from server
/// </summary>
internal CustomBinaryReader StreamReader { get; set; }
/// <summary>
/// Server stream
/// </summary>
internal Stream Stream { get; set; }
/// <summary>
/// Last time this connection was used
/// </summary>
internal DateTime LastAccess { get; set; }
internal TcpConnection()
{
LastAccess = DateTime.Now;
}
}
}
...@@ -10,42 +10,38 @@ using Titanium.Web.Proxy.Helpers; ...@@ -10,42 +10,38 @@ using Titanium.Web.Proxy.Helpers;
using System.Threading; using System.Threading;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
using System.Security.Cryptography.X509Certificates;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network namespace Titanium.Web.Proxy.Network
{ {
public class TcpConnection /// <summary>
{ /// A class that manages Tcp Connection to server used by this proxy server
internal string HostName { get; set; } /// </summary>
internal int port { get; set; }
internal bool IsHttps { get; set; }
internal Version Version { get; set; }
internal TcpClient TcpClient { get; set; }
internal CustomBinaryReader StreamReader { get; set; }
internal Stream Stream { get; set; }
internal DateTime LastAccess { get; set; }
internal TcpConnection()
{
LastAccess = DateTime.Now;
}
}
internal class TcpConnectionManager internal class TcpConnectionManager
{ {
/// <summary>
/// Connection cache
/// </summary>
static Dictionary<string, List<TcpConnection>> connectionCache = new Dictionary<string, List<TcpConnection>>(); static Dictionary<string, List<TcpConnection>> connectionCache = new Dictionary<string, List<TcpConnection>>();
static SemaphoreSlim connectionAccessLock = new SemaphoreSlim(1);
/// <summary>
/// A lock to manage concurrency
/// </summary>
static SemaphoreSlim connectionAccessLock = new SemaphoreSlim(1);
/// <summary>
/// Get a TcpConnection to the specified host, port optionally HTTPS and a particular HTTP version
/// </summary>
/// <param name="hostname"></param>
/// <param name="port"></param>
/// <param name="isHttps"></param>
/// <param name="version"></param>
/// <returns></returns>
internal static async Task<TcpConnection> GetClient(string hostname, int port, bool isHttps, Version version) internal static async Task<TcpConnection> GetClient(string hostname, int port, bool isHttps, Version version)
{ {
List<TcpConnection> cachedConnections = null; List<TcpConnection> cachedConnections = null;
TcpConnection cached = null; TcpConnection cached = null;
//Get a unique string to identify this connection
var key = GetConnectionKey(hostname, port, isHttps, version); var key = GetConnectionKey(hostname, port, isHttps, version);
while (true) while (true)
...@@ -76,10 +72,12 @@ namespace Titanium.Web.Proxy.Network ...@@ -76,10 +72,12 @@ namespace Titanium.Web.Proxy.Network
if (cached == null) if (cached == null)
break; break;
} }
if (cached == null) if (cached == null)
cached = await CreateClient(hostname, port, isHttps, version).ConfigureAwait(false); cached = await CreateClient(hostname, port, isHttps, version);
if (cachedConnections == null || cachedConnections.Count() <= 2) if (cachedConnections == null || cachedConnections.Count() <= 2)
{ {
...@@ -90,11 +88,27 @@ namespace Titanium.Web.Proxy.Network ...@@ -90,11 +88,27 @@ namespace Titanium.Web.Proxy.Network
return cached; return cached;
} }
/// <summary>
/// Get a string to identfiy the connection to a particular host, port
/// </summary>
/// <param name="hostname"></param>
/// <param name="port"></param>
/// <param name="isHttps"></param>
/// <param name="version"></param>
/// <returns></returns>
internal static string GetConnectionKey(string hostname, int port, bool isHttps, Version version) internal static string GetConnectionKey(string hostname, int port, bool isHttps, Version version)
{ {
return string.Format("{0}:{1}:{2}:{3}:{4}", hostname.ToLower(), port, isHttps, version.Major, version.Minor); return string.Format("{0}:{1}:{2}:{3}:{4}", hostname.ToLower(), port, isHttps, version.Major, version.Minor);
} }
/// <summary>
/// Create connection to a particular host/port optionally with SSL and a particular HTTP version
/// </summary>
/// <param name="hostname"></param>
/// <param name="port"></param>
/// <param name="isHttps"></param>
/// <param name="version"></param>
/// <returns></returns>
private static async Task<TcpConnection> CreateClient(string hostname, int port, bool isHttps, Version version) private static async Task<TcpConnection> CreateClient(string hostname, int port, bool isHttps, Version version)
{ {
TcpClient client; TcpClient client;
...@@ -104,6 +118,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -104,6 +118,7 @@ namespace Titanium.Web.Proxy.Network
{ {
SslStream sslStream = null; SslStream sslStream = null;
//If this proxy uses another external proxy then create a tunnel request for HTTPS connections
if (ProxyServer.UpStreamHttpsProxy != null) if (ProxyServer.UpStreamHttpsProxy != null)
{ {
client = new TcpClient(ProxyServer.UpStreamHttpsProxy.HostName, ProxyServer.UpStreamHttpsProxy.Port); client = new TcpClient(ProxyServer.UpStreamHttpsProxy.HostName, ProxyServer.UpStreamHttpsProxy.Port);
...@@ -111,22 +126,22 @@ namespace Titanium.Web.Proxy.Network ...@@ -111,22 +126,22 @@ namespace Titanium.Web.Proxy.Network
using (var writer = new StreamWriter(stream, Encoding.ASCII, ProxyConstants.BUFFER_SIZE, true)) using (var writer = new StreamWriter(stream, Encoding.ASCII, ProxyConstants.BUFFER_SIZE, true))
{ {
await writer.WriteLineAsync(string.Format("CONNECT {0}:{1} {2}", hostname, port, version)).ConfigureAwait(false); await writer.WriteLineAsync(string.Format("CONNECT {0}:{1} {2}", hostname, port, version));
await writer.WriteLineAsync(string.Format("Host: {0}:{1}", hostname, port)).ConfigureAwait(false); await writer.WriteLineAsync(string.Format("Host: {0}:{1}", hostname, port));
await writer.WriteLineAsync("Connection: Keep-Alive").ConfigureAwait(false); await writer.WriteLineAsync("Connection: Keep-Alive");
await writer.WriteLineAsync().ConfigureAwait(false); await writer.WriteLineAsync();
await writer.FlushAsync().ConfigureAwait(false); await writer.FlushAsync();
writer.Close(); writer.Close();
} }
using (var reader = new CustomBinaryReader(stream)) using (var reader = new CustomBinaryReader(stream))
{ {
var result = await reader.ReadLineAsync().ConfigureAwait(false); var result = await reader.ReadLineAsync();
if (!result.ToLower().Contains("200 connection established")) if (!result.ToLower().Contains("200 connection established"))
throw new Exception("Upstream proxy failed to create a secure tunnel"); throw new Exception("Upstream proxy failed to create a secure tunnel");
await reader.ReadAllLinesAsync().ConfigureAwait(false); await reader.ReadAllLinesAsync();
} }
} }
else else
...@@ -139,7 +154,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -139,7 +154,7 @@ namespace Titanium.Web.Proxy.Network
{ {
sslStream = new SslStream(stream, true, new RemoteCertificateValidationCallback(ProxyServer.ValidateServerCertificate), sslStream = new SslStream(stream, true, new RemoteCertificateValidationCallback(ProxyServer.ValidateServerCertificate),
new LocalCertificateSelectionCallback(ProxyServer.SelectClientCertificate)); new LocalCertificateSelectionCallback(ProxyServer.SelectClientCertificate));
await sslStream.AuthenticateAsClientAsync(hostname, null, ProxyConstants.SupportedSslProtocols, false).ConfigureAwait(false); await sslStream.AuthenticateAsClientAsync(hostname, null, ProxyConstants.SupportedSslProtocols, false);
stream = (Stream)sslStream; stream = (Stream)sslStream;
} }
catch catch
...@@ -175,7 +190,11 @@ namespace Titanium.Web.Proxy.Network ...@@ -175,7 +190,11 @@ namespace Titanium.Web.Proxy.Network
}; };
} }
/// <summary>
/// Returns a Tcp Connection back to cache for reuse by other requests
/// </summary>
/// <param name="connection"></param>
/// <returns></returns>
internal static async Task ReleaseClient(TcpConnection connection) internal static async Task ReleaseClient(TcpConnection connection)
{ {
...@@ -199,11 +218,17 @@ namespace Titanium.Web.Proxy.Network ...@@ -199,11 +218,17 @@ namespace Titanium.Web.Proxy.Network
private static bool clearConenctions { get; set; } private static bool clearConenctions { get; set; }
/// <summary>
/// Stop clearing idle connections
/// </summary>
internal static void StopClearIdleConnections() internal static void StopClearIdleConnections()
{ {
clearConenctions = false; clearConenctions = false;
} }
/// <summary>
/// A method to clear idle connections
/// </summary>
internal async static void ClearIdleConnections() internal async static void ClearIdleConnections()
{ {
clearConenctions = true; clearConenctions = true;
...@@ -224,7 +249,8 @@ namespace Titanium.Web.Proxy.Network ...@@ -224,7 +249,8 @@ namespace Titanium.Web.Proxy.Network
} }
finally { connectionAccessLock.Release(); } finally { connectionAccessLock.Release(); }
await Task.Delay(1000 * 60 * 3).ConfigureAwait(false); //every minute run this
await Task.Delay(1000 * 60);
} }
} }
......
...@@ -22,17 +22,41 @@ namespace Titanium.Web.Proxy ...@@ -22,17 +22,41 @@ namespace Titanium.Web.Proxy
static ProxyServer() static ProxyServer()
{ {
ProxyEndPoints = new List<ProxyEndPoint>(); ProxyEndPoints = new List<ProxyEndPoint>();
//default values
ConnectionCacheTimeOutMinutes = 3; ConnectionCacheTimeOutMinutes = 3;
CertificateCacheTimeOutMinutes = 60; CertificateCacheTimeOutMinutes = 60;
} }
/// <summary>
/// Manages certificates used by this proxy
/// </summary>
private static CertificateManager CertManager { get; set; } private static CertificateManager CertManager { get; set; }
/// <summary>
/// Does the root certificate used by this proxy is trusted by the machine?
/// </summary>
private static bool certTrusted { get; set; } private static bool certTrusted { get; set; }
/// <summary>
/// Is the proxy currently running
/// </summary>
private static bool proxyRunning { get; set; } private static bool proxyRunning { get; set; }
/// <summary>
/// Name of the root certificate issuer
/// </summary>
public static string RootCertificateIssuerName { get; set; } public static string RootCertificateIssuerName { get; set; }
/// <summary>
/// Name of the root certificate
/// </summary>
public static string RootCertificateName { get; set; } public static string RootCertificateName { 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
/// </summary>
public static bool Enable100ContinueBehaviour { get; set; } public static bool Enable100ContinueBehaviour { get; set; }
/// <summary> /// <summary>
...@@ -45,13 +69,11 @@ namespace Titanium.Web.Proxy ...@@ -45,13 +69,11 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public static int CertificateCacheTimeOutMinutes { get; set; } public static int CertificateCacheTimeOutMinutes { get; set; }
/// <summary> /// <summary>
/// Intercept request to server /// Intercept request to server
/// </summary> /// </summary>
public static event Func<object, SessionEventArgs, Task> BeforeRequest; public static event Func<object, SessionEventArgs, Task> BeforeRequest;
/// <summary> /// <summary>
/// Intercept response from server /// Intercept response from server
/// </summary> /// </summary>
...@@ -77,20 +99,33 @@ namespace Titanium.Web.Proxy ...@@ -77,20 +99,33 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public static event Func<object, CertificateSelectionEventArgs, Task> ClientCertificateSelectionCallback; public static event Func<object, CertificateSelectionEventArgs, Task> ClientCertificateSelectionCallback;
/// <summary>
/// A list of IpAddress & port this proxy is listening to
/// </summary>
public static List<ProxyEndPoint> ProxyEndPoints { get; set; } public static List<ProxyEndPoint> ProxyEndPoints { get; set; }
/// <summary>
/// Initialize the proxy
/// </summary>
public static void Initialize() public static void Initialize()
{ {
TcpConnectionManager.ClearIdleConnections(); TcpConnectionManager.ClearIdleConnections();
CertManager.ClearIdleCertificates(); CertManager.ClearIdleCertificates();
} }
/// <summary>
/// Quit the proxy
/// </summary>
public static void Quit() public static void Quit()
{ {
TcpConnectionManager.StopClearIdleConnections(); TcpConnectionManager.StopClearIdleConnections();
CertManager.StopClearIdleCertificates(); CertManager.StopClearIdleCertificates();
} }
/// <summary>
/// Add a proxy end point
/// </summary>
/// <param name="endPoint"></param>
public static void AddEndPoint(ProxyEndPoint endPoint) public static void AddEndPoint(ProxyEndPoint endPoint)
{ {
ProxyEndPoints.Add(endPoint); ProxyEndPoints.Add(endPoint);
...@@ -99,6 +134,11 @@ namespace Titanium.Web.Proxy ...@@ -99,6 +134,11 @@ namespace Titanium.Web.Proxy
Listen(endPoint); Listen(endPoint);
} }
/// <summary>
/// Remove a proxy end point
/// Will throw error if the end point does'nt exist
/// </summary>
/// <param name="endPoint"></param>
public static void RemoveEndPoint(ProxyEndPoint endPoint) public static void RemoveEndPoint(ProxyEndPoint endPoint)
{ {
...@@ -111,10 +151,13 @@ namespace Titanium.Web.Proxy ...@@ -111,10 +151,13 @@ namespace Titanium.Web.Proxy
QuitListen(endPoint); QuitListen(endPoint);
} }
/// <summary>
/// Set the given explicit end point as the default proxy server for current machine
/// </summary>
/// <param name="endPoint"></param>
public static void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint) public static void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint)
{ {
VerifyProxy(endPoint); ValidateEndPointAsSystemProxy(endPoint);
//clear any settings previously added //clear any settings previously added
ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpProxy = false); ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpProxy = false);
...@@ -130,14 +173,21 @@ namespace Titanium.Web.Proxy ...@@ -130,14 +173,21 @@ namespace Titanium.Web.Proxy
} }
/// <summary>
/// Remove any HTTP proxy setting of current machien
/// </summary>
public static void DisableSystemHttpProxy() public static void DisableSystemHttpProxy()
{ {
SystemProxyHelper.RemoveHttpProxy(); SystemProxyHelper.RemoveHttpProxy();
} }
/// <summary>
/// Set the given explicit end point as the default proxy server for current machine
/// </summary>
/// <param name="endPoint"></param>
public static void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint) public static void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint)
{ {
VerifyProxy(endPoint); ValidateEndPointAsSystemProxy(endPoint);
if (!endPoint.EnableSsl) if (!endPoint.EnableSsl)
{ {
...@@ -164,16 +214,25 @@ namespace Titanium.Web.Proxy ...@@ -164,16 +214,25 @@ namespace Titanium.Web.Proxy
Console.WriteLine("Set endpoint at Ip {1} and port: {2} as System HTTPS Proxy", endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port); Console.WriteLine("Set endpoint at Ip {1} and port: {2} as System HTTPS Proxy", endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
} }
/// <summary>
/// Remove any HTTPS proxy setting for current machine
/// </summary>
public static void DisableSystemHttpsProxy() public static void DisableSystemHttpsProxy()
{ {
SystemProxyHelper.RemoveHttpsProxy(); SystemProxyHelper.RemoveHttpsProxy();
} }
/// <summary>
/// Clear all proxy settings for current machine
/// </summary>
public static void DisableAllSystemProxies() public static void DisableAllSystemProxies()
{ {
SystemProxyHelper.DisableAllProxy(); SystemProxyHelper.DisableAllProxy();
} }
/// <summary>
/// Start this proxy server
/// </summary>
public static void Start() public static void Start()
{ {
if (proxyRunning) if (proxyRunning)
...@@ -197,6 +256,9 @@ namespace Titanium.Web.Proxy ...@@ -197,6 +256,9 @@ namespace Titanium.Web.Proxy
proxyRunning = true; proxyRunning = true;
} }
/// <summary>
/// Stop this proxy server
/// </summary>
public static void Stop() public static void Stop()
{ {
if (!proxyRunning) if (!proxyRunning)
...@@ -224,6 +286,10 @@ namespace Titanium.Web.Proxy ...@@ -224,6 +286,10 @@ namespace Titanium.Web.Proxy
proxyRunning = false; proxyRunning = false;
} }
/// <summary>
/// Listen on the given end point on local machine
/// </summary>
/// <param name="endPoint"></param>
private static void Listen(ProxyEndPoint endPoint) private static void Listen(ProxyEndPoint endPoint)
{ {
endPoint.listener = new TcpListener(endPoint.IpAddress, endPoint.Port); endPoint.listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
...@@ -234,13 +300,20 @@ namespace Titanium.Web.Proxy ...@@ -234,13 +300,20 @@ namespace Titanium.Web.Proxy
endPoint.listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint); endPoint.listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
} }
/// <summary>
/// Quit listening on the given end point
/// </summary>
/// <param name="endPoint"></param>
private static void QuitListen(ProxyEndPoint endPoint) private static void QuitListen(ProxyEndPoint endPoint)
{ {
endPoint.listener.Stop(); endPoint.listener.Stop();
} }
/// <summary>
private static void VerifyProxy(ExplicitProxyEndPoint endPoint) /// Verifiy if its safe to set this end point as System proxy
/// </summary>
/// <param name="endPoint"></param>
private static void ValidateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint)
{ {
if (ProxyEndPoints.Contains(endPoint) == false) if (ProxyEndPoints.Contains(endPoint) == false)
throw new Exception("Cannot set endPoints not added to proxy as system proxy"); throw new Exception("Cannot set endPoints not added to proxy as system proxy");
...@@ -249,12 +322,17 @@ namespace Titanium.Web.Proxy ...@@ -249,12 +322,17 @@ namespace Titanium.Web.Proxy
throw new Exception("Cannot set system proxy settings before proxy has been started."); throw new Exception("Cannot set system proxy settings before proxy has been started.");
} }
/// <summary>
/// When a connection is received from client act
/// </summary>
/// <param name="asyn"></param>
private static void OnAcceptConnection(IAsyncResult asyn) private static void OnAcceptConnection(IAsyncResult asyn)
{ {
var endPoint = (ProxyEndPoint)asyn.AsyncState; var endPoint = (ProxyEndPoint)asyn.AsyncState;
try try
{ {
//based on end point type call appropriate request handlers
var client = endPoint.listener.EndAcceptTcpClient(asyn); var client = endPoint.listener.EndAcceptTcpClient(asyn);
if (endPoint.GetType() == typeof(TransparentProxyEndPoint)) if (endPoint.GetType() == typeof(TransparentProxyEndPoint))
HandleClient(endPoint as TransparentProxyEndPoint, client); HandleClient(endPoint as TransparentProxyEndPoint, client);
...@@ -273,7 +351,7 @@ namespace Titanium.Web.Proxy ...@@ -273,7 +351,7 @@ namespace Titanium.Web.Proxy
} }
catch catch
{ {
//Other errors are discarded to keep proxy running
} }
} }
......
...@@ -18,7 +18,9 @@ using System.Threading.Tasks; ...@@ -18,7 +18,9 @@ using System.Threading.Tasks;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
/// <summary>
/// Handle the requesr
/// </summary>
partial class ProxyServer partial class ProxyServer
{ {
//This is called when client is aware of proxy //This is called when client is aware of proxy
...@@ -32,7 +34,7 @@ namespace Titanium.Web.Proxy ...@@ -32,7 +34,7 @@ namespace Titanium.Web.Proxy
try try
{ {
//read the first line HTTP command //read the first line HTTP command
var httpCmd = await clientStreamReader.ReadLineAsync().ConfigureAwait(false); var httpCmd = await clientStreamReader.ReadLineAsync();
if (string.IsNullOrEmpty(httpCmd)) if (string.IsNullOrEmpty(httpCmd))
{ {
...@@ -43,6 +45,7 @@ namespace Titanium.Web.Proxy ...@@ -43,6 +45,7 @@ namespace Titanium.Web.Proxy
//break up the line into three components (method, remote URL & Http Version) //break up the line into three components (method, remote URL & Http Version)
var httpCmdSplit = httpCmd.Split(ProxyConstants.SpaceSplit, 3); var httpCmdSplit = httpCmd.Split(ProxyConstants.SpaceSplit, 3);
//Find the request Verb
var httpVerb = httpCmdSplit[0]; var httpVerb = httpCmdSplit[0];
if (httpVerb.ToUpper() == "CONNECT") if (httpVerb.ToUpper() == "CONNECT")
...@@ -50,6 +53,7 @@ namespace Titanium.Web.Proxy ...@@ -50,6 +53,7 @@ namespace Titanium.Web.Proxy
else else
httpRemoteUri = new Uri(httpCmdSplit[1]); httpRemoteUri = new Uri(httpCmdSplit[1]);
//parse the HTTP version
Version version = new Version(1, 1); Version version = new Version(1, 1);
if (httpCmdSplit.Length == 3) if (httpCmdSplit.Length == 3)
{ {
...@@ -59,38 +63,36 @@ namespace Titanium.Web.Proxy ...@@ -59,38 +63,36 @@ namespace Titanium.Web.Proxy
{ {
version = new Version(1, 0); version = new Version(1, 0);
} }
} }
var excluded = endPoint.ExcludedHttpsHostNameRegex != null ? endPoint.ExcludedHttpsHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x)) : false; var excluded = endPoint.ExcludedHttpsHostNameRegex != null ?
endPoint.ExcludedHttpsHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x)) : false;
//Client wants to create a secure tcp tunnel (its a HTTPS request) //Client wants to create a secure tcp tunnel (its a HTTPS request)
if (httpVerb.ToUpper() == "CONNECT" && !excluded && httpRemoteUri.Port != 80) if (httpVerb.ToUpper() == "CONNECT" && !excluded && httpRemoteUri.Port != 80)
{ {
httpRemoteUri = new Uri("https://" + httpCmdSplit[1]); httpRemoteUri = new Uri("https://" + httpCmdSplit[1]);
await clientStreamReader.ReadAllLinesAsync().ConfigureAwait(false); await clientStreamReader.ReadAllLinesAsync();
await WriteConnectResponse(clientStreamWriter, version).ConfigureAwait(false);
await WriteConnectResponse(clientStreamWriter, version);
SslStream sslStream = null; SslStream sslStream = null;
try try
{ {
//create the Tcp Connection to server and then release it to connection cache
var tunnelClient = await TcpConnectionManager.GetClient(httpRemoteUri.Host, httpRemoteUri.Port, true, version).ConfigureAwait(false); //Just doing what CONNECT request is asking as to do
var tunnelClient = await TcpConnectionManager.GetClient(httpRemoteUri.Host, httpRemoteUri.Port, true, version);
await TcpConnectionManager.ReleaseClient(tunnelClient); await TcpConnectionManager.ReleaseClient(tunnelClient);
sslStream = new SslStream(clientStream, true); sslStream = new SslStream(clientStream, true);
var certificate = await CertManager.CreateCertificate(httpRemoteUri.Host, false); var certificate = await CertManager.CreateCertificate(httpRemoteUri.Host, false);
//Successfully managed to authenticate the client using the fake certificate //Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false, await sslStream.AuthenticateAsServerAsync(certificate, false,
ProxyConstants.SupportedSslProtocols, false).ConfigureAwait(false); ProxyConstants.SupportedSslProtocols, false);
//HTTPS server created - we can now decrypt the client's traffic //HTTPS server created - we can now decrypt the client's traffic
clientStream = sslStream; clientStream = sslStream;
clientStreamReader = new CustomBinaryReader(sslStream); clientStreamReader = new CustomBinaryReader(sslStream);
clientStreamWriter = new StreamWriter(sslStream); clientStreamWriter = new StreamWriter(sslStream);
...@@ -104,26 +106,29 @@ namespace Titanium.Web.Proxy ...@@ -104,26 +106,29 @@ namespace Titanium.Web.Proxy
return; return;
} }
//Now read the actual HTTPS request line
httpCmd = await clientStreamReader.ReadLineAsync().ConfigureAwait(false); httpCmd = await clientStreamReader.ReadLineAsync();
} }
//Sorry cannot do a HTTPS request decrypt to port 80 at this time
else if (httpVerb.ToUpper() == "CONNECT") else if (httpVerb.ToUpper() == "CONNECT")
{ {
await clientStreamReader.ReadAllLinesAsync().ConfigureAwait(false); //Cyphen out CONNECT request headers
await WriteConnectResponse(clientStreamWriter, version).ConfigureAwait(false); await clientStreamReader.ReadAllLinesAsync();
//write back successfull CONNECT response
await WriteConnectResponse(clientStreamWriter, version);
//Just relay the request/response without decrypting it
await TcpHelper.SendRaw(clientStream, null, null, httpRemoteUri.Host, httpRemoteUri.Port, await TcpHelper.SendRaw(clientStream, null, null, httpRemoteUri.Host, httpRemoteUri.Port,
false).ConfigureAwait(false); false);
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null); Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
return; return;
} }
//Now create the request //Now create the request
await HandleHttpSessionRequest(client, httpCmd, clientStream, clientStreamReader, clientStreamWriter, await HandleHttpSessionRequest(client, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
httpRemoteUri.Scheme == Uri.UriSchemeHttps ? true : false).ConfigureAwait(false); httpRemoteUri.Scheme == Uri.UriSchemeHttps ? true : false);
} }
catch catch
{ {
...@@ -155,7 +160,7 @@ namespace Titanium.Web.Proxy ...@@ -155,7 +160,7 @@ namespace Titanium.Web.Proxy
{ {
//Successfully managed to authenticate the client using the fake certificate //Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false, await sslStream.AuthenticateAsServerAsync(certificate, false,
SslProtocols.Tls, false).ConfigureAwait(false); SslProtocols.Tls, false);
clientStreamReader = new CustomBinaryReader(sslStream); clientStreamReader = new CustomBinaryReader(sslStream);
clientStreamWriter = new StreamWriter(sslStream); clientStreamWriter = new StreamWriter(sslStream);
...@@ -177,18 +182,29 @@ namespace Titanium.Web.Proxy ...@@ -177,18 +182,29 @@ namespace Titanium.Web.Proxy
clientStreamReader = new CustomBinaryReader(clientStream); clientStreamReader = new CustomBinaryReader(clientStream);
} }
var httpCmd = await clientStreamReader.ReadLineAsync().ConfigureAwait(false); var httpCmd = await clientStreamReader.ReadLineAsync();
//Now create the request //Now create the request
await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter, await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
true).ConfigureAwait(false); true);
} }
/// <summary>
/// This is the core request handler method for a particular connection from client
/// </summary>
/// <param name="client"></param>
/// <param name="httpCmd"></param>
/// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param>
/// <param name="isHttps"></param>
/// <returns></returns>
private static async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream, private static async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, bool isHttps) CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, bool isHttps)
{ {
TcpConnection connection = null; TcpConnection connection = null;
//Loop through each subsequest request on this particular client connection
//(assuming HTTP connection is kept alive by client)
while (true) while (true)
{ {
if (string.IsNullOrEmpty(httpCmd)) if (string.IsNullOrEmpty(httpCmd))
...@@ -207,6 +223,7 @@ namespace Titanium.Web.Proxy ...@@ -207,6 +223,7 @@ namespace Titanium.Web.Proxy
var httpMethod = httpCmdSplit[0]; var httpMethod = httpCmdSplit[0];
//find the request HTTP version
Version version = new Version(1, 1); Version version = new Version(1, 1);
if (httpCmdSplit.Length == 3) if (httpCmdSplit.Length == 3)
{ {
...@@ -220,8 +237,9 @@ namespace Titanium.Web.Proxy ...@@ -220,8 +237,9 @@ namespace Titanium.Web.Proxy
args.WebSession.Request.RequestHeaders = new List<HttpHeader>(); args.WebSession.Request.RequestHeaders = new List<HttpHeader>();
//Read the request headers
string tmpLine; string tmpLine;
while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync().ConfigureAwait(false))) while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync()))
{ {
var header = tmpLine.Split(ProxyConstants.ColonSplit, 2); var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);
args.WebSession.Request.RequestHeaders.Add(new HttpHeader(header[0], header[1])); args.WebSession.Request.RequestHeaders.Add(new HttpHeader(header[0], header[1]));
...@@ -229,12 +247,13 @@ namespace Titanium.Web.Proxy ...@@ -229,12 +247,13 @@ namespace Titanium.Web.Proxy
var httpRemoteUri = new Uri(!isHttps ? httpCmdSplit[1] : (string.Concat("https://", args.WebSession.Request.Host, httpCmdSplit[1]))); var httpRemoteUri = new Uri(!isHttps ? httpCmdSplit[1] : (string.Concat("https://", args.WebSession.Request.Host, httpCmdSplit[1])));
#if DEBUG #if DEBUG
//Just ignore local requests while Debugging
//Its annoying
if (httpRemoteUri.Host.Contains("localhost")) if (httpRemoteUri.Host.Contains("localhost"))
{ {
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null); Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
break; break;
} }
#endif #endif
args.WebSession.Request.RequestUri = httpRemoteUri; args.WebSession.Request.RequestUri = httpRemoteUri;
...@@ -244,11 +263,10 @@ namespace Titanium.Web.Proxy ...@@ -244,11 +263,10 @@ namespace Titanium.Web.Proxy
args.ProxyClient.ClientStreamReader = clientStreamReader; args.ProxyClient.ClientStreamReader = clientStreamReader;
args.ProxyClient.ClientStreamWriter = clientStreamWriter; args.ProxyClient.ClientStreamWriter = clientStreamWriter;
PrepareRequestHeaders(args.WebSession.Request.RequestHeaders, args.WebSession); PrepareRequestHeaders(args.WebSession.Request.RequestHeaders, args.WebSession);
args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority; args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority;
//If requested interception //If user requested interception do it
if (BeforeRequest != null) if (BeforeRequest != null)
{ {
Delegate[] invocationList = BeforeRequest.GetInvocationList(); Delegate[] invocationList = BeforeRequest.GetInvocationList();
...@@ -259,35 +277,39 @@ namespace Titanium.Web.Proxy ...@@ -259,35 +277,39 @@ namespace Titanium.Web.Proxy
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(null, args); handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(null, args);
} }
await Task.WhenAll(handlerTasks).ConfigureAwait(false); await Task.WhenAll(handlerTasks);
} }
//if upgrading to websocket then relay the requet without reading the contents
if (args.WebSession.Request.UpgradeToWebSocket) if (args.WebSession.Request.UpgradeToWebSocket)
{ {
await TcpHelper.SendRaw(clientStream, httpCmd, args.WebSession.Request.RequestHeaders, await TcpHelper.SendRaw(clientStream, httpCmd, args.WebSession.Request.RequestHeaders,
httpRemoteUri.Host, httpRemoteUri.Port, args.IsHttps).ConfigureAwait(false); httpRemoteUri.Host, httpRemoteUri.Port, args.IsHttps);
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args); Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
return; return;
} }
//construct the web request that we are going to issue on behalf of the client. //construct the web request that we are going to issue on behalf of the client.
connection = await TcpConnectionManager.GetClient(args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.IsHttps, version).ConfigureAwait(false); connection = await TcpConnectionManager.GetClient(args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.IsHttps, version);
args.WebSession.Request.RequestLocked = true; args.WebSession.Request.RequestLocked = true;
//If request was cancelled by user then dispose the client
if (args.WebSession.Request.CancelRequest) if (args.WebSession.Request.CancelRequest)
{ {
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args); Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
break; break;
} }
//if expect continue is enabled then send the headers first
//and see if server would return 100 conitinue
if (args.WebSession.Request.ExpectContinue) if (args.WebSession.Request.ExpectContinue)
{ {
args.WebSession.SetConnection(connection); args.WebSession.SetConnection(connection);
await args.WebSession.SendRequest().ConfigureAwait(false); await args.WebSession.SendRequest();
} }
//If 100 continue was the response inform that to the client
if (Enable100ContinueBehaviour) if (Enable100ContinueBehaviour)
if (args.WebSession.Request.Is100Continue) if (args.WebSession.Request.Is100Continue)
{ {
...@@ -302,10 +324,11 @@ namespace Titanium.Web.Proxy ...@@ -302,10 +324,11 @@ namespace Titanium.Web.Proxy
await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
} }
//If expect continue is not enabled then set the connectio and send request headers
if (!args.WebSession.Request.ExpectContinue) if (!args.WebSession.Request.ExpectContinue)
{ {
args.WebSession.SetConnection(connection); args.WebSession.SetConnection(connection);
await args.WebSession.SendRequest().ConfigureAwait(false); await args.WebSession.SendRequest();
} }
//If request was modified by user //If request was modified by user
...@@ -319,7 +342,7 @@ namespace Titanium.Web.Proxy ...@@ -319,7 +342,7 @@ namespace Titanium.Web.Proxy
args.WebSession.Request.ContentLength = args.WebSession.Request.RequestBody.Length; args.WebSession.Request.ContentLength = args.WebSession.Request.RequestBody.Length;
var newStream = args.WebSession.ServerConnection.Stream; var newStream = args.WebSession.ServerConnection.Stream;
await newStream.WriteAsync(args.WebSession.Request.RequestBody, 0, args.WebSession.Request.RequestBody.Length).ConfigureAwait(false); await newStream.WriteAsync(args.WebSession.Request.RequestBody, 0, args.WebSession.Request.RequestBody.Length);
} }
else else
{ {
...@@ -328,14 +351,15 @@ namespace Titanium.Web.Proxy ...@@ -328,14 +351,15 @@ namespace Titanium.Web.Proxy
//If its a post/put request, then read the client html body and send it to server //If its a post/put request, then read the client html body and send it to server
if (httpMethod.ToUpper() == "POST" || httpMethod.ToUpper() == "PUT") if (httpMethod.ToUpper() == "POST" || httpMethod.ToUpper() == "PUT")
{ {
await SendClientRequestBody(args).ConfigureAwait(false); await SendClientRequestBody(args);
} }
} }
} }
//If not expectation failed response was returned by server then parse response
if (!args.WebSession.Request.ExpectationFailed) if (!args.WebSession.Request.ExpectationFailed)
{ {
await HandleHttpSessionResponse(args).ConfigureAwait(false); await HandleHttpSessionResponse(args);
} }
//if connection is closing exit //if connection is closing exit
...@@ -345,10 +369,11 @@ namespace Titanium.Web.Proxy ...@@ -345,10 +369,11 @@ namespace Titanium.Web.Proxy
return; return;
} }
//send the tcp connection to server back to connection cache for reuse
await TcpConnectionManager.ReleaseClient(connection); await TcpConnectionManager.ReleaseClient(connection);
// read the next request // read the next request
httpCmd = await clientStreamReader.ReadLineAsync().ConfigureAwait(false); httpCmd = await clientStreamReader.ReadLineAsync();
} }
catch catch
...@@ -361,14 +386,25 @@ namespace Titanium.Web.Proxy ...@@ -361,14 +386,25 @@ namespace Titanium.Web.Proxy
} }
/// <summary>
/// Write successfull CONNECT response to client
/// </summary>
/// <param name="clientStreamWriter"></param>
/// <param name="httpVersion"></param>
/// <returns></returns>
private static async Task WriteConnectResponse(StreamWriter clientStreamWriter, Version httpVersion) private static async Task WriteConnectResponse(StreamWriter clientStreamWriter, Version httpVersion)
{ {
await clientStreamWriter.WriteLineAsync(string.Format("HTTP/{0}.{1} {2}", httpVersion.Major, httpVersion.Minor, "200 Connection established")).ConfigureAwait(false); await clientStreamWriter.WriteLineAsync(string.Format("HTTP/{0}.{1} {2}", httpVersion.Major, httpVersion.Minor, "200 Connection established"));
await clientStreamWriter.WriteLineAsync(string.Format("Timestamp: {0}", DateTime.Now)).ConfigureAwait(false); await clientStreamWriter.WriteLineAsync(string.Format("Timestamp: {0}", DateTime.Now));
await clientStreamWriter.WriteLineAsync().ConfigureAwait(false); await clientStreamWriter.WriteLineAsync();
await clientStreamWriter.FlushAsync().ConfigureAwait(false); await clientStreamWriter.FlushAsync();
} }
/// <summary>
/// prepare the request headers so that we can avoid encodings not parsable by this proxy
/// </summary>
/// <param name="requestHeaders"></param>
/// <param name="webRequest"></param>
private static void PrepareRequestHeaders(List<HttpHeader> requestHeaders, HttpWebClient webRequest) private static void PrepareRequestHeaders(List<HttpHeader> requestHeaders, HttpWebClient webRequest)
{ {
for (var i = 0; i < requestHeaders.Count; i++) for (var i = 0; i < requestHeaders.Count; i++)
...@@ -387,6 +423,11 @@ namespace Titanium.Web.Proxy ...@@ -387,6 +423,11 @@ namespace Titanium.Web.Proxy
FixRequestProxyHeaders(requestHeaders); FixRequestProxyHeaders(requestHeaders);
webRequest.Request.RequestHeaders = requestHeaders; webRequest.Request.RequestHeaders = requestHeaders;
} }
/// <summary>
/// Fix proxy specific headers
/// </summary>
/// <param name="headers"></param>
private static void FixRequestProxyHeaders(List<HttpHeader> headers) private static void FixRequestProxyHeaders(List<HttpHeader> headers)
{ {
//If proxy-connection close was returned inform to close the connection //If proxy-connection close was returned inform to close the connection
...@@ -405,7 +446,12 @@ namespace Titanium.Web.Proxy ...@@ -405,7 +446,12 @@ namespace Titanium.Web.Proxy
headers.RemoveAll(x => x.Name.ToLower() == "proxy-connection"); headers.RemoveAll(x => x.Name.ToLower() == "proxy-connection");
} }
//This is called when the request is PUT/POST to read the body
/// <summary>
/// This is called when the request is PUT/POST to read the body
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private static async Task SendClientRequestBody(SessionEventArgs args) private static async Task SendClientRequestBody(SessionEventArgs args)
{ {
// End the operation // End the operation
...@@ -415,7 +461,7 @@ namespace Titanium.Web.Proxy ...@@ -415,7 +461,7 @@ namespace Titanium.Web.Proxy
{ {
try try
{ {
await args.ProxyClient.ClientStreamReader.CopyBytesToStream(postStream, args.WebSession.Request.ContentLength).ConfigureAwait(false); await args.ProxyClient.ClientStreamReader.CopyBytesToStream(postStream, args.WebSession.Request.ContentLength);
} }
catch catch
{ {
...@@ -427,7 +473,7 @@ namespace Titanium.Web.Proxy ...@@ -427,7 +473,7 @@ namespace Titanium.Web.Proxy
{ {
try try
{ {
await args.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(postStream).ConfigureAwait(false); await args.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(postStream);
} }
catch catch
{ {
...@@ -436,113 +482,5 @@ namespace Titanium.Web.Proxy ...@@ -436,113 +482,5 @@ namespace Titanium.Web.Proxy
} }
} }
/// <summary>
/// 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 static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (ServerCertificateValidationCallback != null)
{
var args = new CertificateValidationEventArgs();
args.Certificate = certificate;
args.Chain = chain;
args.SslPolicyErrors = sslPolicyErrors;
Delegate[] invocationList = ServerCertificateValidationCallback.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, CertificateValidationEventArgs, Task>)invocationList[i])(null, args);
}
Task.WhenAll(handlerTasks).Wait();
return args.IsValid;
}
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
//By default
//do not allow this client to communicate with unauthenticated servers.
return false;
}
/// <summary>
/// Call back to select client certificate used for mutual authentication
/// </summary>
/// <param name="sender"></param>
/// <param name="certificate"></param>
/// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param>
/// <returns></returns>
internal static X509Certificate SelectClientCertificate(
object sender,
string targetHost,
X509CertificateCollection localCertificates,
X509Certificate remoteCertificate,
string[] acceptableIssuers)
{
X509Certificate clientCertificate = null;
var customSslStream = sender as SslStream;
if (acceptableIssuers != null &&
acceptableIssuers.Length > 0 &&
localCertificates != null &&
localCertificates.Count > 0)
{
// Use the first certificate that is from an acceptable issuer.
foreach (X509Certificate certificate in localCertificates)
{
string issuer = certificate.Issuer;
if (Array.IndexOf(acceptableIssuers, issuer) != -1)
clientCertificate = certificate;
}
}
if (localCertificates != null &&
localCertificates.Count > 0)
clientCertificate = localCertificates[0];
if (ClientCertificateSelectionCallback != null)
{
var args = new CertificateSelectionEventArgs();
args.targetHost = targetHost;
args.localCertificates = localCertificates;
args.remoteCertificate = remoteCertificate;
args.acceptableIssuers = acceptableIssuers;
args.clientCertificate = clientCertificate;
Delegate[] invocationList = ClientCertificateSelectionCallback.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, CertificateSelectionEventArgs, Task>)invocationList[i])(null, args);
}
Task.WhenAll(handlerTasks).Wait();
return args.clientCertificate;
}
return clientCertificate;
}
} }
} }
\ No newline at end of file
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Compression; using Titanium.Web.Proxy.Compression;
using Titanium.Web.Proxy.Shared;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
/// <summary>
/// Handle the response from server
/// </summary>
partial class ProxyServer partial class ProxyServer
{ {
//Called asynchronously when a request was successfully and we received the response //Called asynchronously when a request was successfully and we received the response
public static async Task HandleHttpSessionResponse(SessionEventArgs args) public static async Task HandleHttpSessionResponse(SessionEventArgs args)
{ {
await args.WebSession.ReceiveResponse().ConfigureAwait(false); //read response & headers from server
await args.WebSession.ReceiveResponse();
try try
{ {
if (!args.WebSession.Response.ResponseBodyRead) if (!args.WebSession.Response.ResponseBodyRead)
args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream; args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream;
//If user requested call back then do it
if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked) if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked)
{ {
Delegate[] invocationList = BeforeResponse.GetInvocationList(); Delegate[] invocationList = BeforeResponse.GetInvocationList();
...@@ -37,11 +38,12 @@ namespace Titanium.Web.Proxy ...@@ -37,11 +38,12 @@ namespace Titanium.Web.Proxy
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(null, args); handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(null, args);
} }
await Task.WhenAll(handlerTasks).ConfigureAwait(false); await Task.WhenAll(handlerTasks);
} }
args.WebSession.Response.ResponseLocked = true; args.WebSession.Response.ResponseLocked = true;
//Write back to client 100-conitinue response if that's what server returned
if (args.WebSession.Response.Is100Continue) if (args.WebSession.Response.Is100Continue)
{ {
await WriteResponseStatus(args.WebSession.Response.HttpVersion, "100", await WriteResponseStatus(args.WebSession.Response.HttpVersion, "100",
...@@ -55,6 +57,7 @@ namespace Titanium.Web.Proxy ...@@ -55,6 +57,7 @@ namespace Titanium.Web.Proxy
await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
} }
//Write back response status to client
await WriteResponseStatus(args.WebSession.Response.HttpVersion, args.WebSession.Response.ResponseStatusCode, await WriteResponseStatus(args.WebSession.Response.HttpVersion, args.WebSession.Response.ResponseStatusCode,
args.WebSession.Response.ResponseStatusDescription, args.ProxyClient.ClientStreamWriter); args.WebSession.Response.ResponseStatusDescription, args.ProxyClient.ClientStreamWriter);
...@@ -65,7 +68,7 @@ namespace Titanium.Web.Proxy ...@@ -65,7 +68,7 @@ namespace Titanium.Web.Proxy
if (contentEncoding != null) if (contentEncoding != null)
{ {
args.WebSession.Response.ResponseBody = await GetCompressedResponseBody(contentEncoding, args.WebSession.Response.ResponseBody).ConfigureAwait(false); args.WebSession.Response.ResponseBody = await GetCompressedResponseBody(contentEncoding, args.WebSession.Response.ResponseBody);
if (isChunked == false) if (isChunked == false)
args.WebSession.Response.ContentLength = args.WebSession.Response.ResponseBody.Length; args.WebSession.Response.ContentLength = args.WebSession.Response.ResponseBody.Length;
...@@ -73,8 +76,8 @@ namespace Titanium.Web.Proxy ...@@ -73,8 +76,8 @@ namespace Titanium.Web.Proxy
args.WebSession.Response.ContentLength = -1; args.WebSession.Response.ContentLength = -1;
} }
await WriteResponseHeaders(args.ProxyClient.ClientStreamWriter, args.WebSession.Response.ResponseHeaders).ConfigureAwait(false); await WriteResponseHeaders(args.ProxyClient.ClientStreamWriter, args.WebSession.Response.ResponseHeaders);
await WriteResponseBody(args.ProxyClient.ClientStream, args.WebSession.Response.ResponseBody, isChunked).ConfigureAwait(false); await args.ProxyClient.ClientStream.WriteResponseBody(args.WebSession.Response.ResponseBody, isChunked);
} }
else else
{ {
...@@ -82,7 +85,7 @@ namespace Titanium.Web.Proxy ...@@ -82,7 +85,7 @@ namespace Titanium.Web.Proxy
if (args.WebSession.Response.IsChunked || args.WebSession.Response.ContentLength > 0 || if (args.WebSession.Response.IsChunked || args.WebSession.Response.ContentLength > 0 ||
(args.WebSession.Response.HttpVersion.Major == 1 && args.WebSession.Response.HttpVersion.Minor == 0)) (args.WebSession.Response.HttpVersion.Major == 1 && args.WebSession.Response.HttpVersion.Minor == 0))
await WriteResponseBody(args.WebSession.ServerConnection.StreamReader, args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked, args.WebSession.Response.ContentLength).ConfigureAwait(false); await args.WebSession.ServerConnection.StreamReader.WriteResponseBody(args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked, args.WebSession.Response.ContentLength);
} }
await args.ProxyClient.ClientStream.FlushAsync(); await args.ProxyClient.ClientStream.FlushAsync();
...@@ -98,20 +101,39 @@ namespace Titanium.Web.Proxy ...@@ -98,20 +101,39 @@ namespace Titanium.Web.Proxy
} }
} }
/// <summary>
/// get the compressed response body from give response bytes
/// </summary>
/// <param name="encodingType"></param>
/// <param name="responseBodyStream"></param>
/// <returns></returns>
private static async Task<byte[]> GetCompressedResponseBody(string encodingType, byte[] responseBodyStream) private static async Task<byte[]> GetCompressedResponseBody(string encodingType, byte[] responseBodyStream)
{ {
var compressionFactory = new CompressionFactory(); var compressionFactory = new CompressionFactory();
var compressor = compressionFactory.Create(encodingType); var compressor = compressionFactory.Create(encodingType);
return await compressor.Compress(responseBodyStream).ConfigureAwait(false); return await compressor.Compress(responseBodyStream);
} }
/// <summary>
/// Write response status
/// </summary>
/// <param name="version"></param>
/// <param name="code"></param>
/// <param name="description"></param>
/// <param name="responseWriter"></param>
/// <returns></returns>
private static async Task WriteResponseStatus(Version version, string code, string description, private static async Task WriteResponseStatus(Version version, string code, string description,
StreamWriter responseWriter) StreamWriter responseWriter)
{ {
await responseWriter.WriteLineAsync(string.Format("HTTP/{0}.{1} {2} {3}", version.Major, version.Minor, code, description)); await responseWriter.WriteLineAsync(string.Format("HTTP/{0}.{1} {2} {3}", version.Major, version.Minor, code, description));
} }
/// <summary>
/// Write response headers to client
/// </summary>
/// <param name="responseWriter"></param>
/// <param name="headers"></param>
/// <returns></returns>
private static async Task WriteResponseHeaders(StreamWriter responseWriter, List<HttpHeader> headers) private static async Task WriteResponseHeaders(StreamWriter responseWriter, List<HttpHeader> headers)
{ {
if (headers != null) if (headers != null)
...@@ -127,6 +149,11 @@ namespace Titanium.Web.Proxy ...@@ -127,6 +149,11 @@ namespace Titanium.Web.Proxy
await responseWriter.WriteLineAsync(); await responseWriter.WriteLineAsync();
await responseWriter.FlushAsync(); await responseWriter.FlushAsync();
} }
/// <summary>
/// Fix the proxy specific headers before sending response headers to client
/// </summary>
/// <param name="headers"></param>
private static void FixResponseProxyHeaders(List<HttpHeader> headers) private static void FixResponseProxyHeaders(List<HttpHeader> headers)
{ {
//If proxy-connection close was returned inform to close the connection //If proxy-connection close was returned inform to close the connection
...@@ -146,95 +173,14 @@ namespace Titanium.Web.Proxy ...@@ -146,95 +173,14 @@ namespace Titanium.Web.Proxy
headers.RemoveAll(x => x.Name.ToLower() == "proxy-connection"); headers.RemoveAll(x => x.Name.ToLower() == "proxy-connection");
} }
private static async Task WriteResponseBody(Stream clientStream, byte[] data, bool isChunked) /// <summary>
{ /// Handle dispose of a client/server session
if (!isChunked) /// </summary>
{ /// <param name="client"></param>
await clientStream.WriteAsync(data, 0, data.Length).ConfigureAwait(false); /// <param name="clientStream"></param>
} /// <param name="clientStreamReader"></param>
else /// <param name="clientStreamWriter"></param>
await WriteResponseBodyChunked(data, clientStream).ConfigureAwait(false); /// <param name="args"></param>
}
private static async Task WriteResponseBody(CustomBinaryReader inStreamReader, Stream outStream, bool isChunked, long ContentLength)
{
if (!isChunked)
{
//http 1.0
if (ContentLength == -1)
ContentLength = long.MaxValue;
int bytesToRead = ProxyConstants.BUFFER_SIZE;
if (ContentLength < ProxyConstants.BUFFER_SIZE)
bytesToRead = (int)ContentLength;
var buffer = new byte[ProxyConstants.BUFFER_SIZE];
var bytesRead = 0;
var totalBytesRead = 0;
while ((bytesRead += await inStreamReader.BaseStream.ReadAsync(buffer, 0, bytesToRead).ConfigureAwait(false)) > 0)
{
await outStream.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false);
totalBytesRead += bytesRead;
if (totalBytesRead == ContentLength)
break;
bytesRead = 0;
var remainingBytes = (ContentLength - totalBytesRead);
bytesToRead = remainingBytes > (long)ProxyConstants.BUFFER_SIZE ? ProxyConstants.BUFFER_SIZE : (int)remainingBytes;
}
}
else
await WriteResponseBodyChunked(inStreamReader, outStream).ConfigureAwait(false);
}
//Send chunked response
private static async Task WriteResponseBodyChunked(CustomBinaryReader inStreamReader, Stream outStream)
{
while (true)
{
var chunkHead = await inStreamReader.ReadLineAsync().ConfigureAwait(false);
var chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
if (chunkSize != 0)
{
var buffer = await inStreamReader.ReadBytesAsync(chunkSize).ConfigureAwait(false);
var chunkHeadBytes = Encoding.ASCII.GetBytes(chunkSize.ToString("x2"));
await outStream.WriteAsync(chunkHeadBytes, 0, chunkHeadBytes.Length).ConfigureAwait(false);
await outStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length).ConfigureAwait(false);
await outStream.WriteAsync(buffer, 0, chunkSize).ConfigureAwait(false);
await outStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length).ConfigureAwait(false);
await inStreamReader.ReadLineAsync().ConfigureAwait(false);
}
else
{
await inStreamReader.ReadLineAsync().ConfigureAwait(false);
await outStream.WriteAsync(ProxyConstants.ChunkEnd, 0, ProxyConstants.ChunkEnd.Length).ConfigureAwait(false);
break;
}
}
}
private static async Task WriteResponseBodyChunked(byte[] data, Stream outStream)
{
var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2"));
await outStream.WriteAsync(chunkHead, 0, chunkHead.Length).ConfigureAwait(false);
await outStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length).ConfigureAwait(false);
await outStream.WriteAsync(data, 0, data.Length).ConfigureAwait(false);
await outStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length).ConfigureAwait(false);
await outStream.WriteAsync(ProxyConstants.ChunkEnd, 0, ProxyConstants.ChunkEnd.Length).ConfigureAwait(false);
}
private static void Dispose(TcpClient client, IDisposable clientStream, IDisposable clientStreamReader, private static void Dispose(TcpClient client, IDisposable clientStream, IDisposable clientStreamReader,
IDisposable clientStreamWriter, IDisposable args) IDisposable clientStreamWriter, IDisposable args)
{ {
......
...@@ -49,6 +49,7 @@ ...@@ -49,6 +49,7 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="CertificateHandler.cs" />
<Compile Include="Compression\CompressionFactory.cs" /> <Compile Include="Compression\CompressionFactory.cs" />
<Compile Include="Compression\DeflateCompression.cs" /> <Compile Include="Compression\DeflateCompression.cs" />
<Compile Include="Compression\GZipCompression.cs" /> <Compile Include="Compression\GZipCompression.cs" />
...@@ -62,11 +63,13 @@ ...@@ -62,11 +63,13 @@
<Compile Include="Decompression\ZlibDecompression.cs" /> <Compile Include="Decompression\ZlibDecompression.cs" />
<Compile Include="EventArguments\CertificateSelectionEventArgs.cs" /> <Compile Include="EventArguments\CertificateSelectionEventArgs.cs" />
<Compile Include="EventArguments\CertificateValidationEventArgs.cs" /> <Compile Include="EventArguments\CertificateValidationEventArgs.cs" />
<Compile Include="Extensions\ByteArrayExtensions.cs" />
<Compile Include="Network\CachedCertificate.cs" />
<Compile Include="Network\ProxyClient.cs" /> <Compile Include="Network\ProxyClient.cs" />
<Compile Include="Exceptions\BodyNotFoundException.cs" /> <Compile Include="Exceptions\BodyNotFoundException.cs" />
<Compile Include="Extensions\HttpWebResponseExtensions.cs" /> <Compile Include="Extensions\HttpWebResponseExtensions.cs" />
<Compile Include="Extensions\HttpWebRequestExtensions.cs" /> <Compile Include="Extensions\HttpWebRequestExtensions.cs" />
<Compile Include="Helpers\CertificateManager.cs" /> <Compile Include="Network\CertificateManager.cs" />
<Compile Include="Helpers\Firefox.cs" /> <Compile Include="Helpers\Firefox.cs" />
<Compile Include="Helpers\SystemProxy.cs" /> <Compile Include="Helpers\SystemProxy.cs" />
<Compile Include="Models\EndPoint.cs" /> <Compile Include="Models\EndPoint.cs" />
...@@ -74,6 +77,7 @@ ...@@ -74,6 +77,7 @@
<Compile Include="Http\Request.cs" /> <Compile Include="Http\Request.cs" />
<Compile Include="Http\Response.cs" /> <Compile Include="Http\Response.cs" />
<Compile Include="Models\ExternalProxy.cs" /> <Compile Include="Models\ExternalProxy.cs" />
<Compile Include="Network\TcpConnection.cs" />
<Compile Include="Network\TcpConnectionManager.cs" /> <Compile Include="Network\TcpConnectionManager.cs" />
<Compile Include="Models\HttpHeader.cs" /> <Compile Include="Models\HttpHeader.cs" />
<Compile Include="Http\HttpWebClient.cs" /> <Compile Include="Http\HttpWebClient.cs" />
......
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