Commit 82326b01 authored by Jehonathan's avatar Jehonathan Committed by GitHub

Merge pull request #82 from justcoding121/master

sync with master
parents e1cd5fcd 703f5219
...@@ -15,6 +15,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -15,6 +15,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
ProxyServer.BeforeRequest += OnRequest; ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse; ProxyServer.BeforeResponse += OnResponse;
ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation; ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
ProxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
//Exclude Https addresses you don't want to proxy //Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning //Usefull for clients that use certificate pinning
...@@ -129,13 +130,25 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -129,13 +130,25 @@ namespace Titanium.Web.Proxy.Examples.Basic
/// </summary> /// </summary>
/// <param name="sender"></param> /// <param name="sender"></param>
/// <param name="e"></param> /// <param name="e"></param>
public async Task OnCertificateValidation(object sender, CertificateValidationEventArgs e) public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{ {
//set IsValid to true/false based on Certificate Errors //set IsValid to true/false based on Certificate Errors
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None) if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
e.IsValid = true; e.IsValid = true;
else
await e.Session.Ok("Cannot validate server certificate! Not safe to proceed."); return Task.FromResult(0);
}
/// <summary>
/// Allows overriding default client certificate selection logic during mutual authentication
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e)
{
//set e.clientCertificate to override
return Task.FromResult(0);
} }
} }
} }
\ No newline at end of file
...@@ -12,8 +12,11 @@ Features ...@@ -12,8 +12,11 @@ Features
======== ========
* Supports Http(s) and most features of HTTP 1.1 * Supports Http(s) and most features of HTTP 1.1
* Supports relaying of WebSockets * Support redirect/block/update requests
* Supports script injection * Supports updating response
* Safely relays WebSocket requests over Http
* Support mutual SSL authentication
* Fully asynchronous proxy
Usage Usage
===== =====
...@@ -35,6 +38,8 @@ Setup HTTP proxy: ...@@ -35,6 +38,8 @@ Setup HTTP proxy:
ProxyServer.BeforeRequest += OnRequest; ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse; ProxyServer.BeforeResponse += OnResponse;
ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation; ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
ProxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
//Exclude Https addresses you don't want to proxy //Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning //Usefull for clients that use certificate pinning
...@@ -84,9 +89,7 @@ Setup HTTP proxy: ...@@ -84,9 +89,7 @@ Setup HTTP proxy:
``` ```
Sample request and response event handlers Sample request and response event handlers
```csharp ```csharp
//intecept & cancel, redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e) public async Task OnRequest(object sender, SessionEventArgs e)
{ {
Console.WriteLine(e.WebSession.Request.Url); Console.WriteLine(e.WebSession.Request.Url);
...@@ -148,20 +151,27 @@ Sample request and response event handlers ...@@ -148,20 +151,27 @@ Sample request and response event handlers
} }
} }
/// Allows overriding default certificate validation logic
/// Allows overriding default certificate validation logic public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
public async Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{ {
//set IsValid to true/false based on Certificate Errors //set IsValid to true/false based on Certificate Errors
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None) if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
e.IsValid = true; e.IsValid = true;
else
await e.Session.Ok("Cannot validate server certificate! Not safe to proceed."); return Task.FromResult(0);
}
/// Allows overriding default client certificate selection logic during mutual authentication
public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e)
{
//set e.clientCertificate to override
return Task.FromResult(0);
} }
``` ```
Future roadmap Future roadmap
============ ============
* Support mutual authentication * Implement Kerberos/NTLM authentication over HTTP protocols for windows domain
* Support Server Name Indication (SNI) for transparent endpoints * Support Server Name Indication (SNI) for transparent endpoints
* Support HTTP 2.0 * Support HTTP 2.0
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
{ {
class DecompressionFactory /// <summary>
/// A factory to generate the de-compression methods based on the type of compression
/// </summary>
internal class DecompressionFactory
{ {
public IDecompression Create(string type) internal IDecompression Create(string type)
{ {
switch(type) switch(type)
{ {
......
...@@ -2,7 +2,11 @@ ...@@ -2,7 +2,11 @@
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
class DefaultDecompression : IDecompression
/// <summary>
/// When no compression is specified just return the byte array
/// </summary>
internal class DefaultDecompression : IDecompression
{ {
public Task<byte[]> Decompress(byte[] compressedArray) public Task<byte[]> Decompress(byte[] compressedArray)
{ {
......
...@@ -5,7 +5,10 @@ using Titanium.Web.Proxy.Shared; ...@@ -5,7 +5,10 @@ using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
class DeflateDecompression : IDecompression /// <summary>
/// concrete implementation of deflate de-compression
/// </summary>
internal class DeflateDecompression : IDecompression
{ {
public async Task<byte[]> Decompress(byte[] compressedArray) public async Task<byte[]> Decompress(byte[] compressedArray)
{ {
...@@ -13,14 +16,14 @@ namespace Titanium.Web.Proxy.Decompression ...@@ -13,14 +16,14 @@ namespace Titanium.Web.Proxy.Decompression
using (var decompressor = new DeflateStream(stream, CompressionMode.Decompress)) using (var decompressor = new DeflateStream(stream, CompressionMode.Decompress))
{ {
var buffer = new byte[Constants.BUFFER_SIZE]; var buffer = new byte[ProxyConstants.BUFFER_SIZE];
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,19 +5,22 @@ using Titanium.Web.Proxy.Shared; ...@@ -5,19 +5,22 @@ using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
class GZipDecompression : IDecompression /// <summary>
/// concrete implementation of gzip de-compression
/// </summary>
internal class GZipDecompression : IDecompression
{ {
public async Task<byte[]> Decompress(byte[] compressedArray) public async Task<byte[]> Decompress(byte[] compressedArray)
{ {
using (var decompressor = new GZipStream(new MemoryStream(compressedArray), CompressionMode.Decompress)) using (var decompressor = new GZipStream(new MemoryStream(compressedArray), CompressionMode.Decompress))
{ {
var buffer = new byte[Constants.BUFFER_SIZE]; var buffer = new byte[ProxyConstants.BUFFER_SIZE];
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
{ {
interface IDecompression /// <summary>
/// An interface for decompression
/// </summary>
internal interface IDecompression
{ {
Task<byte[]> Decompress(byte[] compressedArray); Task<byte[]> Decompress(byte[] compressedArray);
} }
} }
...@@ -5,21 +5,24 @@ using Titanium.Web.Proxy.Shared; ...@@ -5,21 +5,24 @@ using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression namespace Titanium.Web.Proxy.Decompression
{ {
class ZlibDecompression : IDecompression /// <summary>
/// concrete implemetation of zlib de-compression
/// </summary>
internal class ZlibDecompression : IDecompression
{ {
public async Task<byte[]> Decompress(byte[] compressedArray) public async Task<byte[]> Decompress(byte[] compressedArray)
{ {
var memoryStream = new MemoryStream(compressedArray); var memoryStream = new MemoryStream(compressedArray);
using (var decompressor = new ZlibStream(memoryStream, CompressionMode.Decompress)) using (var decompressor = new ZlibStream(memoryStream, CompressionMode.Decompress))
{ {
var buffer = new byte[Constants.BUFFER_SIZE]; var buffer = new byte[ProxyConstants.BUFFER_SIZE];
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;
using System.Security.Cryptography.X509Certificates;
namespace Titanium.Web.Proxy.EventArguments
{
/// <summary>
/// An argument passed on to user for client certificate selection during mutual SSL authentication
/// </summary>
public class CertificateSelectionEventArgs : EventArgs, IDisposable
{
public object sender { get; internal set; }
public string targetHost { get; internal set; }
public X509CertificateCollection localCertificates { get; internal set; }
public X509Certificate remoteCertificate { get; internal set; }
public string[] acceptableIssuers { get; internal set; }
public X509Certificate clientCertificate { get; set; }
public void Dispose()
{
throw new NotImplementedException();
}
}
}
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Security; using System.Net.Security;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Http;
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 string HostName => Session.WebSession.Request.Host;
public SessionEventArgs Session { get; internal set; }
public X509Certificate Certificate { get; internal set; } public X509Certificate Certificate { get; internal set; }
public X509Chain Chain { get; internal set; } public X509Chain Chain { get; internal set; }
public SslPolicyErrors SslPolicyErrors { get; internal set; } public SslPolicyErrors SslPolicyErrors { get; internal set; }
......
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;
}
}
}
...@@ -7,10 +7,14 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -7,10 +7,14 @@ namespace Titanium.Web.Proxy.Extensions
/// <summary> /// <summary>
/// Extensions on HttpWebSession object /// Extensions on HttpWebSession object
/// </summary> /// </summary>
public static class HttpWebRequestExtensions internal static class HttpWebRequestExtensions
{ {
//Get encoding of the HTTP request /// <summary>
public static Encoding GetEncoding(this Request request) /// parse the character encoding of request from request headers
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
internal static Encoding GetEncoding(this Request request)
{ {
try try
{ {
...@@ -19,7 +23,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -19,7 +23,7 @@ namespace Titanium.Web.Proxy.Extensions
return Encoding.GetEncoding("ISO-8859-1"); return Encoding.GetEncoding("ISO-8859-1");
//extract the encoding by finding the charset //extract the encoding by finding the charset
var contentTypes = request.ContentType.Split(Constants.SemiColonSplit); var contentTypes = request.ContentType.Split(ProxyConstants.SemiColonSplit);
foreach (var contentType in contentTypes) foreach (var contentType in contentTypes)
{ {
var encodingSplit = contentType.Split('='); var encodingSplit = contentType.Split('=');
......
...@@ -4,9 +4,14 @@ using Titanium.Web.Proxy.Shared; ...@@ -4,9 +4,14 @@ using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
public static class HttpWebResponseExtensions internal static class HttpWebResponseExtensions
{ {
public static Encoding GetResponseCharacterEncoding(this Response response) /// <summary>
/// Gets the character encoding of response from response headers
/// </summary>
/// <param name="response"></param>
/// <returns></returns>
internal static Encoding GetResponseCharacterEncoding(this Response response)
{ {
try try
{ {
...@@ -15,7 +20,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -15,7 +20,7 @@ namespace Titanium.Web.Proxy.Extensions
return Encoding.GetEncoding("ISO-8859-1"); return Encoding.GetEncoding("ISO-8859-1");
//extract the encoding by finding the charset //extract the encoding by finding the charset
var contentTypes = response.ContentType.Split(Constants.SemiColonSplit); var contentTypes = response.ContentType.Split(ProxyConstants.SemiColonSplit);
foreach (var contentType in contentTypes) foreach (var contentType in contentTypes)
{ {
var encodingSplit = contentType.Split('='); var encodingSplit = contentType.Split('=');
......
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,9 +7,19 @@ using Titanium.Web.Proxy.Shared; ...@@ -8,9 +7,19 @@ using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
public static class StreamHelper /// <summary>
/// Extensions used for Stream and CustomBinaryReader objects
/// </summary>
internal static class StreamExtensions
{ {
public static async Task CopyToAsync(this Stream input, string initialData, Stream output) /// <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)
{ {
if (!string.IsNullOrEmpty(initialData)) if (!string.IsNullOrEmpty(initialData))
{ {
...@@ -19,18 +28,24 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -19,18 +28,24 @@ 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;
long bytesToRead; long bytesToRead;
if (totalBytesToRead < Constants.BUFFER_SIZE) if (totalBytesToRead < ProxyConstants.BUFFER_SIZE)
{ {
bytesToRead = totalBytesToRead; bytesToRead = totalBytesToRead;
} }
else else
bytesToRead = Constants.BUFFER_SIZE; bytesToRead = ProxyConstants.BUFFER_SIZE;
while (totalbytesRead < totalBytesToRead) while (totalbytesRead < totalBytesToRead)
...@@ -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
{
await clientStreamReader.ReadLineAsync();
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 else
{ {
await clientStreamReader.ReadLineAsync().ConfigureAwait(false); await inStreamReader.ReadLineAsync();
await outStream.WriteAsync(ProxyConstants.ChunkEnd, 0, ProxyConstants.ChunkEnd.Length);
break; 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
using System.Net.Sockets; using System.Net.Sockets;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
internal static class TcpExtensions internal static class TcpExtensions
{ {
public static bool IsConnected(this Socket client) /// <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)
{ {
// This is how you can determine whether a socket is still connected. // This is how you can determine whether a socket is still connected.
bool blockingState = client.Blocking; bool blockingState = client.Blocking;
......
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
/// as well as to read bytes as required /// as well as to read bytes as required
/// </summary> /// </summary>
public 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;
}
public Stream BaseStream => stream; internal Stream BaseStream => stream;
/// <summary> /// <summary>
/// Read a line from the byte stream /// Read a line from the byte stream
...@@ -34,32 +35,39 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -34,32 +35,39 @@ 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
{ {
var lastChar = default(char); try
var buffer = new byte[1];
while (await this.stream.ReadAsync(buffer, 0, 1).ConfigureAwait(false) > 0)
{ {
if (lastChar == '\r' && buffer[0] == '\n') var lastChar = default(char);
{ var buffer = new byte[1];
return readBuffer.Remove(readBuffer.Length - 1, 1).ToString();
} while ((await this.stream.ReadAsync(buffer, 0, 1)) > 0)
if (buffer[0] == '\0')
{ {
return readBuffer.ToString(); //if new line
if (lastChar == '\r' && buffer[0] == '\n')
{
var result = readBuffer.ToArray();
return encoding.GetString(result.SubArray(0, result.Length - 1));
}
//end of stream
if (buffer[0] == '\0')
{
return encoding.GetString(readBuffer.ToArray());
}
await readBuffer.WriteAsync(buffer,0,1);
//store last char for new line comparison
lastChar = (char)buffer[0];
} }
readBuffer.Append((char)buffer[0]);
lastChar = (char)buffer[0];
}
return readBuffer.ToString(); return encoding.GetString(readBuffer.ToArray());
} }
catch (IOException) catch (IOException)
{ {
return readBuffer.ToString(); throw;
}
} }
} }
...@@ -71,30 +79,35 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -71,30 +79,35 @@ 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 = Constants.BUFFER_SIZE; int bytesToRead = ProxyConstants.BUFFER_SIZE;
if (totalBytesToRead < Constants.BUFFER_SIZE) if (totalBytesToRead < ProxyConstants.BUFFER_SIZE)
bytesToRead = (int)totalBytesToRead; bytesToRead = (int)totalBytesToRead;
var buffer = new byte[Constants.BUFFER_SIZE]; var buffer = new byte[ProxyConstants.BUFFER_SIZE];
var bytesRead = 0; var bytesRead = 0;
var totalBytesRead = 0; var totalBytesRead = 0;
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)
...@@ -102,7 +115,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -102,7 +115,7 @@ namespace Titanium.Web.Proxy.Helpers
bytesRead = 0; bytesRead = 0;
var remainingBytes = (totalBytesToRead - totalBytesRead); var remainingBytes = (totalBytesToRead - totalBytesRead);
bytesToRead = remainingBytes > (long)Constants.BUFFER_SIZE ? Constants.BUFFER_SIZE : (int)remainingBytes; bytesToRead = remainingBytes > (long)ProxyConstants.BUFFER_SIZE ? ProxyConstants.BUFFER_SIZE : (int)remainingBytes;
} }
return outStream.ToArray(); return outStream.ToArray();
......
...@@ -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")]
...@@ -14,27 +18,27 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -14,27 +18,27 @@ namespace Titanium.Web.Proxy.Helpers
int dwBufferLength); int dwBufferLength);
} }
internal class HttpSystemProxyValue internal class HttpSystemProxyValue
{ {
public string HostName { get; set; } internal string HostName { get; set; }
public int Port { get; set; } internal int Port { get; set; }
public bool IsSecure { get; set; } internal bool IsHttps { get; set; }
public override string ToString() public override string ToString()
{ {
if (!IsSecure) if (!IsHttps)
return "http=" + HostName + ":" + Port; return "http=" + HostName + ":" + Port;
else else
return "https=" + HostName + ":" + Port; return "https=" + HostName + ":" + Port;
} }
} }
public static class SystemProxyHelper internal static class SystemProxyHelper
{ {
public const int InternetOptionSettingsChanged = 39; internal const int InternetOptionSettingsChanged = 39;
public const int InternetOptionRefresh = 37; internal const int InternetOptionRefresh = 37;
public static void SetHttpProxy(string hostname, int port) internal static void SetHttpProxy(string hostname, int port)
{ {
var reg = Registry.CurrentUser.OpenSubKey( var reg = Registry.CurrentUser.OpenSubKey(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true); "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true);
...@@ -44,11 +48,11 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -44,11 +48,11 @@ namespace Titanium.Web.Proxy.Helpers
var exisitingContent = reg.GetValue("ProxyServer") as string; var exisitingContent = reg.GetValue("ProxyServer") as string;
var existingSystemProxyValues = GetSystemProxyValues(exisitingContent); var existingSystemProxyValues = GetSystemProxyValues(exisitingContent);
existingSystemProxyValues.RemoveAll(x => !x.IsSecure); existingSystemProxyValues.RemoveAll(x => !x.IsHttps);
existingSystemProxyValues.Add(new HttpSystemProxyValue() existingSystemProxyValues.Add(new HttpSystemProxyValue()
{ {
HostName = hostname, HostName = hostname,
IsSecure = false, IsHttps = false,
Port = port Port = port
}); });
...@@ -59,19 +63,21 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -59,19 +63,21 @@ namespace Titanium.Web.Proxy.Helpers
Refresh(); Refresh();
} }
/// <summary>
public static void RemoveHttpProxy() /// Remove the http proxy setting from current machine
/// </summary>
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;
var existingSystemProxyValues = GetSystemProxyValues(exisitingContent); var existingSystemProxyValues = GetSystemProxyValues(exisitingContent);
existingSystemProxyValues.RemoveAll(x => !x.IsSecure); existingSystemProxyValues.RemoveAll(x => !x.IsHttps);
if (!(existingSystemProxyValues.Count() == 0)) if (!(existingSystemProxyValues.Count() == 0))
{ {
...@@ -89,11 +95,16 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -89,11 +95,16 @@ namespace Titanium.Web.Proxy.Helpers
Refresh(); Refresh();
} }
public static void SetHttpsProxy(string hostname, int port) /// <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)
{ {
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)
{ {
prepareRegistry(reg); prepareRegistry(reg);
...@@ -101,11 +112,11 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -101,11 +112,11 @@ namespace Titanium.Web.Proxy.Helpers
var exisitingContent = reg.GetValue("ProxyServer") as string; var exisitingContent = reg.GetValue("ProxyServer") as string;
var existingSystemProxyValues = GetSystemProxyValues(exisitingContent); var existingSystemProxyValues = GetSystemProxyValues(exisitingContent);
existingSystemProxyValues.RemoveAll(x => x.IsSecure); existingSystemProxyValues.RemoveAll(x => x.IsHttps);
existingSystemProxyValues.Add(new HttpSystemProxyValue() existingSystemProxyValues.Add(new HttpSystemProxyValue()
{ {
HostName = hostname, HostName = hostname,
IsSecure = true, IsHttps = true,
Port = port Port = port
}); });
...@@ -116,7 +127,10 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -116,7 +127,10 @@ namespace Titanium.Web.Proxy.Helpers
Refresh(); Refresh();
} }
public static void RemoveHttpsProxy() /// <summary>
/// Removes the https proxy setting to nothing
/// </summary>
internal static void RemoveHttpsProxy()
{ {
var reg = Registry.CurrentUser.OpenSubKey( var reg = Registry.CurrentUser.OpenSubKey(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true); "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true);
...@@ -127,7 +141,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -127,7 +141,7 @@ namespace Titanium.Web.Proxy.Helpers
var exisitingContent = reg.GetValue("ProxyServer") as string; var exisitingContent = reg.GetValue("ProxyServer") as string;
var existingSystemProxyValues = GetSystemProxyValues(exisitingContent); var existingSystemProxyValues = GetSystemProxyValues(exisitingContent);
existingSystemProxyValues.RemoveAll(x => x.IsSecure); existingSystemProxyValues.RemoveAll(x => x.IsHttps);
if (!(existingSystemProxyValues.Count() == 0)) if (!(existingSystemProxyValues.Count() == 0))
{ {
...@@ -139,14 +153,17 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -139,14 +153,17 @@ namespace Titanium.Web.Proxy.Helpers
reg.SetValue("ProxyEnable", 0); reg.SetValue("ProxyEnable", 0);
reg.SetValue("ProxyServer", string.Empty); reg.SetValue("ProxyServer", string.Empty);
} }
} }
} }
Refresh(); Refresh();
} }
public static void DisableAllProxy() /// <summary>
/// Removes all types of proxy settings (both http & https)
/// </summary>
internal static void DisableAllProxy()
{ {
var reg = Registry.CurrentUser.OpenSubKey( var reg = Registry.CurrentUser.OpenSubKey(
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true); "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true);
...@@ -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();
...@@ -197,7 +225,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -197,7 +225,7 @@ namespace Titanium.Web.Proxy.Helpers
{ {
HostName = endPoint.Split(':')[0], HostName = endPoint.Split(':')[0],
Port = int.Parse(endPoint.Split(':')[1]), Port = int.Parse(endPoint.Split(':')[1]),
IsSecure = false IsHttps = false
}; };
} }
else if (tmp.StartsWith("https=")) else if (tmp.StartsWith("https="))
...@@ -207,13 +235,16 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -207,13 +235,16 @@ namespace Titanium.Web.Proxy.Helpers
{ {
HostName = endPoint.Split(':')[0], HostName = endPoint.Split(':')[0],
Port = int.Parse(endPoint.Split(':')[1]), Port = int.Parse(endPoint.Split(':')[1]),
IsSecure = true IsHttps = true
}; };
} }
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)
...@@ -227,8 +258,10 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -227,8 +258,10 @@ 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
{ {
public class TcpHelper
internal class TcpHelper
{ {
public async static Task SendRaw(Stream clientStream, string httpCmd, List<HttpHeader> requestHeaders, string hostName, /// <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,
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);
...@@ -50,7 +63,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -50,7 +63,7 @@ namespace Titanium.Web.Proxy.Helpers
try try
{ {
sslStream = new SslStream(tunnelStream); sslStream = new SslStream(tunnelStream);
await sslStream.AuthenticateAsClientAsync(hostName, null, Constants.SupportedProtocols, false); await sslStream.AuthenticateAsClientAsync(hostName, null, ProxyConstants.SupportedSslProtocols, false);
tunnelStream = sslStream; tunnelStream = sslStream;
} }
catch catch
...@@ -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,14 +9,23 @@ using Titanium.Web.Proxy.Shared; ...@@ -9,14 +9,23 @@ using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
public class HttpWebSession /// <summary>
/// Used to communicate with the server over HTTP(S)
/// </summary>
public class HttpWebClient
{ {
/// <summary>
/// Connection to server
/// </summary>
internal TcpConnection ServerConnection { get; set; } internal TcpConnection ServerConnection { get; set; }
public Request Request { get; set; } public Request Request { get; set; }
public Response Response { get; set; } public Response Response { get; set; }
public bool IsSecure /// <summary>
/// Is Https?
/// </summary>
public bool IsHttps
{ {
get get
{ {
...@@ -24,31 +33,40 @@ namespace Titanium.Web.Proxy.Http ...@@ -24,31 +33,40 @@ 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;
ServerConnection = Connection; ServerConnection = Connection;
} }
internal HttpWebSession() internal HttpWebClient()
{ {
this.Request = new Request(); this.Request = new Request();
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,
this.Request.HttpVersion 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);
...@@ -64,7 +82,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -64,7 +82,7 @@ namespace Titanium.Web.Proxy.Http
if (ProxyServer.Enable100ContinueBehaviour) if (ProxyServer.Enable100ContinueBehaviour)
if (this.Request.ExpectContinue) if (this.Request.ExpectContinue)
{ {
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(Constants.SpaceSplit, 3); var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
var responseStatusCode = httpResult[1].Trim(); var responseStatusCode = httpResult[1].Trim();
var responseStatusDescription = httpResult[2].Trim(); var responseStatusDescription = httpResult[2].Trim();
...@@ -73,30 +91,41 @@ namespace Titanium.Web.Proxy.Http ...@@ -73,30 +91,41 @@ 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
if (this.Response.ResponseStatusCode != null) return; if (this.Response.ResponseStatusCode != null) return;
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(Constants.SpaceSplit, 3); var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
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 version = new Version(1,1);
if (httpVersion == "http/1.0")
{
version = new Version(1, 0);
} }
this.Response.HttpVersion = httpResult[0].Trim(); this.Response.HttpVersion = version;
this.Response.ResponseStatusCode = httpResult[1].Trim(); this.Response.ResponseStatusCode = httpResult[1].Trim();
this.Response.ResponseStatusDescription = httpResult[2].Trim(); this.Response.ResponseStatusDescription = httpResult[2].Trim();
...@@ -106,7 +135,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -106,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;
} }
...@@ -115,16 +144,17 @@ namespace Titanium.Web.Proxy.Http ...@@ -115,16 +144,17 @@ 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)
{ {
string[] strArray = responseLines[index].Split(Constants.ColonSplit, 2); string[] strArray = responseLines[index].Split(ProxyConstants.ColonSplit, 2);
this.Response.ResponseHeaders.Add(new HttpHeader(strArray[0], strArray[1])); this.Response.ResponseHeaders.Add(new HttpHeader(strArray[0], strArray[1]));
} }
} }
......
...@@ -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; }
public string HttpVersion { get; set; }
/// <summary>
/// Request Http Version
/// </summary>
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()
......
...@@ -4,10 +4,13 @@ using System.Linq; ...@@ -4,10 +4,13 @@ using System.Linq;
using System.Text; using System.Text;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
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; }
...@@ -15,7 +18,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -15,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
...@@ -31,7 +36,11 @@ namespace Titanium.Web.Proxy.Http ...@@ -31,7 +36,11 @@ namespace Titanium.Web.Proxy.Http
} }
} }
internal string HttpVersion { get; set; } internal Version HttpVersion { get; set; }
/// <summary>
/// Keep the connection alive?
/// </summary>
internal bool ResponseKeepAlive internal bool ResponseKeepAlive
{ {
get get
...@@ -48,6 +57,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -48,6 +57,9 @@ namespace Titanium.Web.Proxy.Http
} }
} }
/// <summary>
/// Content type of this response
/// </summary>
public string ContentType public string ContentType
{ {
get get
...@@ -64,6 +76,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -64,6 +76,9 @@ namespace Titanium.Web.Proxy.Http
} }
} }
/// <summary>
/// Length of response body
/// </summary>
internal long ContentLength internal long ContentLength
{ {
get get
...@@ -102,6 +117,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -102,6 +117,9 @@ namespace Titanium.Web.Proxy.Http
} }
} }
/// <summary>
/// Response transfer-encoding is chunked?
/// </summary>
internal bool IsChunked internal bool IsChunked
{ {
get get
...@@ -140,14 +158,37 @@ namespace Titanium.Web.Proxy.Http ...@@ -140,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;
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Security;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
namespace Titanium.Web.Proxy.Network
{
/// <summary>
/// Used to pass in Session object for ServerCertificateValidation Callback
/// </summary>
internal class CustomSslStream : SslStream
{
/// <summary>
/// Holds the current session
/// </summary>
internal SessionEventArgs Session { get; set; }
public CustomSslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback)
:base(innerStream, leaveInnerStreamOpen, userCertificateValidationCallback)
{
}
}
}
...@@ -5,27 +5,27 @@ using Titanium.Web.Proxy.Helpers; ...@@ -5,27 +5,27 @@ using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Network namespace Titanium.Web.Proxy.Network
{ {
/// <summary> /// <summary>
/// This class wraps Tcp connection to Server /// This class wraps Tcp connection to client
/// </summary> /// </summary>
public class ProxyClient public class ProxyClient
{ {
/// <summary> /// <summary>
/// TcpClient used to communicate with server /// TcpClient used to communicate with client
/// </summary> /// </summary>
internal TcpClient TcpClient { get; set; } internal TcpClient TcpClient { get; set; }
/// <summary> /// <summary>
/// holds the stream to server /// holds the stream to client
/// </summary> /// </summary>
internal Stream ClientStream { get; set; } internal Stream ClientStream { get; set; }
/// <summary> /// <summary>
/// Used to read line by line from server /// Used to read line by line from client
/// </summary> /// </summary>
internal CustomBinaryReader ClientStreamReader { get; set; } internal CustomBinaryReader ClientStreamReader { get; set; }
/// <summary> /// <summary>
/// used to write line by line to server /// used to write line by line to client
/// </summary> /// </summary>
internal StreamWriter ClientStreamWriter { get; set; } internal StreamWriter ClientStreamWriter { get; set; }
......
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;
}
}
}
...@@ -22,19 +22,61 @@ namespace Titanium.Web.Proxy ...@@ -22,19 +22,61 @@ namespace Titanium.Web.Proxy
static ProxyServer() static ProxyServer()
{ {
ProxyEndPoints = new List<ProxyEndPoint>(); ProxyEndPoints = new List<ProxyEndPoint>();
Initialize();
//default values
ConnectionCacheTimeOutMinutes = 3;
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>
/// Minutes TCP connection cache to servers to be kept alive when in idle state
/// </summary>
public static int ConnectionCacheTimeOutMinutes { get; set; }
/// <summary>
/// Minutes certificates should be kept in cache when not used
/// </summary>
public static int CertificateCacheTimeOutMinutes { get; set; }
/// <summary>
/// Intercept request to server
/// </summary>
public static event Func<object, SessionEventArgs, Task> BeforeRequest; public static event Func<object, SessionEventArgs, Task> BeforeRequest;
/// <summary>
/// Intercept response from server
/// </summary>
public static event Func<object, SessionEventArgs, Task> BeforeResponse; public static event Func<object, SessionEventArgs, Task> BeforeResponse;
/// <summary> /// <summary>
...@@ -51,15 +93,39 @@ namespace Titanium.Web.Proxy ...@@ -51,15 +93,39 @@ namespace Titanium.Web.Proxy
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication /// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication
/// </summary> /// </summary>
public static event Func<object, CertificateValidationEventArgs, Task> ServerCertificateValidationCallback; public static event Func<object, CertificateValidationEventArgs, Task> ServerCertificateValidationCallback;
/// <summary>
/// Callback tooverride client certificate during SSL mutual authentication
/// </summary>
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();
}
/// <summary>
/// Quit the proxy
/// </summary>
public static void Quit()
{
TcpConnectionManager.StopClearIdleConnections();
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);
...@@ -68,6 +134,11 @@ namespace Titanium.Web.Proxy ...@@ -68,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)
{ {
...@@ -80,10 +151,13 @@ namespace Titanium.Web.Proxy ...@@ -80,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);
...@@ -99,14 +173,21 @@ namespace Titanium.Web.Proxy ...@@ -99,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)
{ {
...@@ -133,16 +214,25 @@ namespace Titanium.Web.Proxy ...@@ -133,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)
...@@ -153,7 +243,7 @@ namespace Titanium.Web.Proxy ...@@ -153,7 +243,7 @@ namespace Titanium.Web.Proxy
CertManager = new CertificateManager(RootCertificateIssuerName, CertManager = new CertificateManager(RootCertificateIssuerName,
RootCertificateName); RootCertificateName);
certTrusted = CertManager.CreateTrustedRootCertificate().Result; certTrusted = CertManager.CreateTrustedRootCertificate().Result;
foreach (var endPoint in ProxyEndPoints) foreach (var endPoint in ProxyEndPoints)
...@@ -161,9 +251,14 @@ namespace Titanium.Web.Proxy ...@@ -161,9 +251,14 @@ namespace Titanium.Web.Proxy
Listen(endPoint); Listen(endPoint);
} }
Initialize();
proxyRunning = true; proxyRunning = true;
} }
/// <summary>
/// Stop this proxy server
/// </summary>
public static void Stop() public static void Stop()
{ {
if (!proxyRunning) if (!proxyRunning)
...@@ -186,9 +281,15 @@ namespace Titanium.Web.Proxy ...@@ -186,9 +281,15 @@ namespace Titanium.Web.Proxy
CertManager.Dispose(); CertManager.Dispose();
Quit();
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);
...@@ -199,13 +300,20 @@ namespace Titanium.Web.Proxy ...@@ -199,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");
...@@ -214,12 +322,17 @@ namespace Titanium.Web.Proxy ...@@ -214,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);
...@@ -236,6 +349,10 @@ namespace Titanium.Web.Proxy ...@@ -236,6 +349,10 @@ namespace Titanium.Web.Proxy
// so just return. // so just return.
return; return;
} }
catch
{
//Other errors are discarded to keep proxy running
}
} }
......
This diff is collapsed.
This diff is collapsed.
using System; using System;
using System.Security.Authentication; using System.Security.Authentication;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
namespace Titanium.Web.Proxy.Shared namespace Titanium.Web.Proxy.Shared
{ {
/// <summary> /// <summary>
/// Literals shared by Proxy Server /// Literals shared by Proxy Server
/// </summary> /// </summary>
internal class Constants internal class ProxyConstants
{ {
public static readonly int BUFFER_SIZE = 8192;
internal static readonly char[] SpaceSplit = { ' ' }; internal static readonly char[] SpaceSplit = { ' ' };
internal static readonly char[] ColonSplit = { ':' }; internal static readonly char[] ColonSplit = { ':' };
internal static readonly char[] SemiColonSplit = { ';' }; internal static readonly char[] SemiColonSplit = { ';' };
...@@ -21,6 +18,8 @@ namespace Titanium.Web.Proxy.Shared ...@@ -21,6 +18,8 @@ namespace Titanium.Web.Proxy.Shared
internal static readonly byte[] ChunkEnd = internal static readonly byte[] ChunkEnd =
Encoding.ASCII.GetBytes(0.ToString("x2") + Environment.NewLine + Environment.NewLine); Encoding.ASCII.GetBytes(0.ToString("x2") + Environment.NewLine + Environment.NewLine);
internal static SslProtocols SupportedProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3; public static SslProtocols SupportedSslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
public static readonly int BUFFER_SIZE = 8192;
} }
} }
...@@ -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" />
...@@ -60,12 +61,15 @@ ...@@ -60,12 +61,15 @@
<Compile Include="Decompression\GZipDecompression.cs" /> <Compile Include="Decompression\GZipDecompression.cs" />
<Compile Include="Decompression\IDecompression.cs" /> <Compile Include="Decompression\IDecompression.cs" />
<Compile Include="Decompression\ZlibDecompression.cs" /> <Compile Include="Decompression\ZlibDecompression.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" />
...@@ -73,7 +77,7 @@ ...@@ -73,7 +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\CustomSslStream.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" />
...@@ -87,7 +91,7 @@ ...@@ -87,7 +91,7 @@
<Compile Include="Extensions\StreamExtensions.cs" /> <Compile Include="Extensions\StreamExtensions.cs" />
<Compile Include="Http\Responses\OkResponse.cs" /> <Compile Include="Http\Responses\OkResponse.cs" />
<Compile Include="Http\Responses\RedirectResponse.cs" /> <Compile Include="Http\Responses\RedirectResponse.cs" />
<Compile Include="Shared\Constants.cs" /> <Compile Include="Shared\ProxyConstants.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup />
<ItemGroup> <ItemGroup>
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
# - Section names should be unique on each level. # - Section names should be unique on each level.
# version format # version format
version: 2.2{build} version: 2.3000.{build}
shallow_clone: true shallow_clone: true
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment