Commit 2316ea2d authored by Jehonathan's avatar Jehonathan Committed by GitHub

Merge pull request #142 from justcoding121/release

Merge Beta with Stable branch
parents 14016520 fa9a02c4
...@@ -13,6 +13,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -13,6 +13,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
public ProxyTestController() public ProxyTestController()
{ {
proxyServer = new ProxyServer(); proxyServer = new ProxyServer();
proxyServer.TrustRootCertificate = true;
} }
public void StartProxy() public void StartProxy()
...@@ -115,6 +116,9 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -115,6 +116,9 @@ namespace Titanium.Web.Proxy.Examples.Basic
//read response headers //read response headers
var responseHeaders = e.WebSession.Response.ResponseHeaders; var responseHeaders = e.WebSession.Response.ResponseHeaders;
// print out process id of current session
Console.WriteLine($"PID: {e.WebSession.ProcessId.Value}");
//if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return; //if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST") if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST")
{ {
......
Doneness:
- [ ] Build is okay - I made sure that this change is building successfully.
- [ ] No Bugs - I made sure that this change is working properly as expected. It does'nt have any bugs that you are aware of.
- [ ] Branching - If this is not a hotfix, I am making this request against release branch (aka beta branch)
...@@ -17,6 +17,8 @@ Features ...@@ -17,6 +17,8 @@ Features
* Safely relays WebSocket requests over Http * Safely relays WebSocket requests over Http
* Support mutual SSL authentication * Support mutual SSL authentication
* Fully asynchronous proxy * Fully asynchronous proxy
* Supports proxy authentication
Usage Usage
===== =====
...@@ -25,18 +27,22 @@ Refer the HTTP Proxy Server library in your project, look up Test project to lea ...@@ -25,18 +27,22 @@ Refer the HTTP Proxy Server library in your project, look up Test project to lea
Install by nuget: Install by nuget:
Install-Package Titanium.Web.Proxy For beta releases on [release branch](https://github.com/justcoding121/Titanium-Web-Proxy/tree/release)
After installing nuget package mark following files to be copied to app directory Install-Package Titanium.Web.Proxy -Pre
* makecert.exe For stable releases on [master branch](https://github.com/justcoding121/Titanium-Web-Proxy/tree/master)
Install-Package Titanium.Web.Proxy
Setup HTTP proxy: Setup HTTP proxy:
```csharp ```csharp
var proxyServer = new ProxyServer(); var proxyServer = new ProxyServer();
//locally trust root certificate used by this proxy
proxyServer.TrustRootCertificate = true;
proxyServer.BeforeRequest += OnRequest; proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse; proxyServer.BeforeResponse += OnResponse;
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation; proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
...@@ -69,8 +75,8 @@ Setup HTTP proxy: ...@@ -69,8 +75,8 @@ Setup HTTP proxy:
}; };
proxyServer.AddEndPoint(transparentEndPoint); proxyServer.AddEndPoint(transparentEndPoint);
//proxyServer.ExternalHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 }; //proxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//proxyServer.ExternalHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 }; //proxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
foreach (var endPoint in proxyServer.ProxyEndPoints) foreach (var endPoint in proxyServer.ProxyEndPoints)
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ", Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
...@@ -176,7 +182,10 @@ Sample request and response event handlers ...@@ -176,7 +182,10 @@ Sample request and response event handlers
``` ```
Future roadmap Future roadmap
============ ============
* 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
* Support upstream AutoProxy detection
* Support SOCKS protocol
* Implement Kerberos/NTLM authentication over HTTP protocols for windows domain
...@@ -20,7 +20,8 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -20,7 +20,8 @@ namespace Titanium.Web.Proxy.UnitTests
{ {
var tasks = new List<Task>(); var tasks = new List<Task>();
var mgr = new CertificateManager("Titanium","Titanium Root Certificate Authority"); var mgr = new CertificateManager("Titanium", "Titanium Root Certificate Authority",
new Lazy<Action<Exception>>(() => (e => { })).Value);
mgr.ClearIdleCertificates(1); mgr.ClearIdleCertificates(1);
...@@ -33,9 +34,9 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -33,9 +34,9 @@ namespace Titanium.Web.Proxy.UnitTests
await Task.Delay(random.Next(0, 10) * 1000); await Task.Delay(random.Next(0, 10) * 1000);
//get the connection //get the connection
var certificate = await mgr.CreateCertificate(host, false); var certificate = mgr.CreateCertificate(host, false);
Assert.IsNotNull(certificate); Assert.IsNotNull(certificate);
})); }));
......
using System;
using System.Net;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.UnitTests
{
[TestClass]
public class ProxyServerTests
{
[TestMethod]
public void GivenOneEndpointIsAlreadyAddedToAddress_WhenAddingNewEndpointToExistingAddress_ThenExceptionIsThrown()
{
// Arrange
var proxy = new ProxyServer();
const int port = 9999;
var firstIpAddress = IPAddress.Parse("127.0.0.1");
var secondIpAddress = IPAddress.Parse("127.0.0.1");
proxy.AddEndPoint(new ExplicitProxyEndPoint(firstIpAddress, port, false));
// Act
try
{
proxy.AddEndPoint(new ExplicitProxyEndPoint(secondIpAddress, port, false));
}
catch (Exception exc)
{
// Assert
StringAssert.Contains(exc.Message, "Cannot add another endpoint to same port");
return;
}
Assert.Fail("An exception should be thrown by now");
}
[TestMethod]
public void GivenOneEndpointIsAlreadyAddedToAddress_WhenAddingNewEndpointToExistingAddress_ThenTwoEndpointsExists()
{
// Arrange
var proxy = new ProxyServer();
const int port = 9999;
var firstIpAddress = IPAddress.Parse("127.0.0.1");
var secondIpAddress = IPAddress.Parse("192.168.1.1");
proxy.AddEndPoint(new ExplicitProxyEndPoint(firstIpAddress, port, false));
// Act
proxy.AddEndPoint(new ExplicitProxyEndPoint(secondIpAddress, port, false));
// Assert
Assert.AreEqual(2, proxy.ProxyEndPoints.Count);
}
[TestMethod]
public void GivenOneEndpointIsAlreadyAddedToPort_WhenAddingNewEndpointToExistingPort_ThenExceptionIsThrown()
{
// Arrange
var proxy = new ProxyServer();
const int port = 9999;
proxy.AddEndPoint(new ExplicitProxyEndPoint(IPAddress.Loopback, port, false));
// Act
try
{
proxy.AddEndPoint(new ExplicitProxyEndPoint(IPAddress.Loopback, port, false));
}
catch (Exception exc)
{
// Assert
StringAssert.Contains(exc.Message, "Cannot add another endpoint to same port");
return;
}
Assert.Fail("An exception should be thrown by now");
}
[TestMethod]
public void GivenOneEndpointIsAlreadyAddedToZeroPort_WhenAddingNewEndpointToExistingPort_ThenTwoEndpointsExists()
{
// Arrange
var proxy = new ProxyServer();
const int port = 0;
proxy.AddEndPoint(new ExplicitProxyEndPoint(IPAddress.Loopback, port, false));
// Act
proxy.AddEndPoint(new ExplicitProxyEndPoint(IPAddress.Loopback, port, false));
// Assert
Assert.AreEqual(2, proxy.ProxyEndPoints.Count);
}
}
}
...@@ -52,6 +52,7 @@ ...@@ -52,6 +52,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="CertificateManagerTests.cs" /> <Compile Include="CertificateManagerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ProxyServerTests.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj"> <ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
......
...@@ -60,11 +60,12 @@ namespace Titanium.Web.Proxy ...@@ -60,11 +60,12 @@ namespace Titanium.Web.Proxy
/// Call back to select client certificate used for mutual authentication /// Call back to select client certificate used for mutual authentication
/// </summary> /// </summary>
/// <param name="sender"></param> /// <param name="sender"></param>
/// <param name="certificate"></param> /// <param name="targetHost"></param>
/// <param name="chain"></param> /// <param name="localCertificates"></param>
/// <param name="sslPolicyErrors"></param> /// <param name="remoteCertificate"></param>
/// <param name="acceptableIssuers"></param>
/// <returns></returns> /// <returns></returns>
internal X509Certificate SelectClientCertificate( internal X509Certificate SelectClientCertificate(
object sender, object sender,
string targetHost, string targetHost,
X509CertificateCollection localCertificates, X509CertificateCollection localCertificates,
...@@ -101,11 +102,11 @@ namespace Titanium.Web.Proxy ...@@ -101,11 +102,11 @@ namespace Titanium.Web.Proxy
{ {
var args = new CertificateSelectionEventArgs(); var args = new CertificateSelectionEventArgs();
args.targetHost = targetHost; args.TargetHost = targetHost;
args.localCertificates = localCertificates; args.LocalCertificates = localCertificates;
args.remoteCertificate = remoteCertificate; args.RemoteCertificate = remoteCertificate;
args.acceptableIssuers = acceptableIssuers; args.AcceptableIssuers = acceptableIssuers;
args.clientCertificate = clientCertificate; args.ClientCertificate = clientCertificate;
Delegate[] invocationList = ClientCertificateSelectionCallback.GetInvocationList(); Delegate[] invocationList = ClientCertificateSelectionCallback.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length]; Task[] handlerTasks = new Task[invocationList.Length];
...@@ -117,7 +118,7 @@ namespace Titanium.Web.Proxy ...@@ -117,7 +118,7 @@ namespace Titanium.Web.Proxy
Task.WhenAll(handlerTasks).Wait(); Task.WhenAll(handlerTasks).Wait();
return args.clientCertificate; return args.ClientCertificate;
} }
return clientCertificate; return clientCertificate;
......
...@@ -6,19 +6,15 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -6,19 +6,15 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary> /// <summary>
/// An argument passed on to user for client certificate selection during mutual SSL authentication /// An argument passed on to user for client certificate selection during mutual SSL authentication
/// </summary> /// </summary>
public class CertificateSelectionEventArgs : EventArgs, IDisposable public class CertificateSelectionEventArgs : EventArgs
{ {
public object sender { get; internal set; } public object Sender { get; internal set; }
public string targetHost { get; internal set; } public string TargetHost { get; internal set; }
public X509CertificateCollection localCertificates { get; internal set; } public X509CertificateCollection LocalCertificates { get; internal set; }
public X509Certificate remoteCertificate { get; internal set; } public X509Certificate RemoteCertificate { get; internal set; }
public string[] acceptableIssuers { get; internal set; } public string[] AcceptableIssuers { get; internal set; }
public X509Certificate clientCertificate { get; set; } public X509Certificate ClientCertificate { get; set; }
public void Dispose()
{
throw new NotImplementedException();
}
} }
} }
using System; using System;
using System.IO; using System.Collections.Generic;
using System.Text; using System.IO;
using Titanium.Web.Proxy.Exceptions; using System.Text;
using Titanium.Web.Proxy.Decompression; using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Decompression;
using Titanium.Web.Proxy.Http.Responses; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Http.Responses;
using System.Threading.Tasks; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Network; using System.Threading.Tasks;
using System.Net; using Titanium.Web.Proxy.Network;
using System.Net;
namespace Titanium.Web.Proxy.EventArguments using Titanium.Web.Proxy.Models;
{
/// <summary> namespace Titanium.Web.Proxy.EventArguments
/// Holds info related to a single proxy session (single request/response sequence) {
/// A proxy session is bounded to a single connection from client /// <summary>
/// A proxy session ends when client terminates connection to proxy /// Holds info related to a single proxy session (single request/response sequence)
/// or when server terminates connection from proxy /// A proxy session is bounded to a single connection from client
/// </summary> /// A proxy session ends when client terminates connection to proxy
public class SessionEventArgs : EventArgs, IDisposable /// or when server terminates connection from proxy
{ /// </summary>
public class SessionEventArgs : EventArgs, IDisposable
/// <summary> {
/// Size of Buffers used by this object
/// </summary> /// <summary>
private readonly int bufferSize; /// Size of Buffers used by this object
/// </summary>
/// <summary> private readonly int bufferSize;
/// Holds a reference to proxy response handler method
/// </summary> /// <summary>
private readonly Func<SessionEventArgs, Task> httpResponseHandler; /// Holds a reference to proxy response handler method
/// </summary>
/// <summary> private readonly Func<SessionEventArgs, Task> httpResponseHandler;
/// Holds a reference to client
/// </summary> /// <summary>
internal ProxyClient ProxyClient { get; set; } /// Holds a reference to client
/// </summary>
/// <summary> internal ProxyClient ProxyClient { get; set; }
/// Does this session uses SSL
/// </summary> //Should we send a rerequest
public bool IsHttps => WebSession.Request.RequestUri.Scheme == Uri.UriSchemeHttps; public bool ReRequest
{
get;
public IPEndPoint ClientEndPoint => (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint; set;
}
/// <summary>
/// A web session corresponding to a single request/response sequence /// <summary>
/// within a proxy connection /// Does this session uses SSL
/// </summary> /// </summary>
public HttpWebClient WebSession { get; set; } public bool IsHttps => WebSession.Request.RequestUri.Scheme == Uri.UriSchemeHttps;
/// <summary> public IPEndPoint ClientEndPoint => (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint;
/// Constructor to initialize the proxy
/// </summary> /// <summary>
internal SessionEventArgs(int bufferSize, Func<SessionEventArgs, Task> httpResponseHandler) /// A web session corresponding to a single request/response sequence
{ /// within a proxy connection
this.bufferSize = bufferSize; /// </summary>
this.httpResponseHandler = httpResponseHandler; public HttpWebClient WebSession { get; set; }
ProxyClient = new ProxyClient(); public ExternalProxy CustomUpStreamHttpProxyUsed { get; set; }
WebSession = new HttpWebClient();
} public ExternalProxy CustomUpStreamHttpsProxyUsed { get; set; }
/// <summary> /// <summary>
/// Read request body content as bytes[] for current session /// Constructor to initialize the proxy
/// </summary> /// </summary>
private async Task ReadRequestBody() internal SessionEventArgs(int bufferSize, Func<SessionEventArgs, Task> httpResponseHandler)
{ {
//GET request don't have a request body to read this.bufferSize = bufferSize;
if ((WebSession.Request.Method.ToUpper() != "POST" && WebSession.Request.Method.ToUpper() != "PUT")) this.httpResponseHandler = httpResponseHandler;
{
throw new BodyNotFoundException("Request don't have a body." + ProxyClient = new ProxyClient();
"Please verify that this request is a Http POST/PUT and request " + WebSession = new HttpWebClient();
"content length is greater than zero before accessing the body."); }
}
/// <summary>
//Caching check /// Read request body content as bytes[] for current session
if (WebSession.Request.RequestBody == null) /// </summary>
{ private async Task ReadRequestBody()
{
//If chunked then its easy just read the whole body with the content length mentioned in the request header //GET request don't have a request body to read
using (var requestBodyStream = new MemoryStream()) if ((WebSession.Request.Method.ToUpper() != "POST" && WebSession.Request.Method.ToUpper() != "PUT"))
{ {
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol throw new BodyNotFoundException("Request don't have a body." +
if (WebSession.Request.IsChunked) "Please verify that this request is a Http POST/PUT and request " +
{ "content length is greater than zero before accessing the body.");
await this.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(bufferSize, requestBodyStream); }
}
else //Caching check
{ if (WebSession.Request.RequestBody == null)
//If not chunked then its easy just read the whole body with the content length mentioned in the request header {
if (WebSession.Request.ContentLength > 0)
{ //If chunked then its easy just read the whole body with the content length mentioned in the request header
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response using (var requestBodyStream = new MemoryStream())
await this.ProxyClient.ClientStreamReader.CopyBytesToStream(bufferSize, requestBodyStream, {
WebSession.Request.ContentLength); //For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if (WebSession.Request.IsChunked)
} {
else if (WebSession.Request.HttpVersion.Major == 1 && WebSession.Request.HttpVersion.Minor == 0) await this.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(bufferSize, requestBodyStream);
{ }
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, requestBodyStream, long.MaxValue); else
} {
} //If not chunked then its easy just read the whole body with the content length mentioned in the request header
WebSession.Request.RequestBody = await GetDecompressedResponseBody(WebSession.Request.ContentEncoding, if (WebSession.Request.ContentLength > 0)
requestBodyStream.ToArray()); {
} //If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await this.ProxyClient.ClientStreamReader.CopyBytesToStream(bufferSize, requestBodyStream,
//Now set the flag to true WebSession.Request.ContentLength);
//So that next time we can deliver body from cache
WebSession.Request.RequestBodyRead = true; }
} else if (WebSession.Request.HttpVersion.Major == 1 && WebSession.Request.HttpVersion.Minor == 0)
{
} await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, requestBodyStream, long.MaxValue);
}
/// <summary> }
/// Read response body as byte[] for current response WebSession.Request.RequestBody = await GetDecompressedResponseBody(WebSession.Request.ContentEncoding,
/// </summary> requestBodyStream.ToArray());
private async Task ReadResponseBody() }
{
//If not already read (not cached yet) //Now set the flag to true
if (WebSession.Response.ResponseBody == null) //So that next time we can deliver body from cache
{ WebSession.Request.RequestBodyRead = true;
using (var responseBodyStream = new MemoryStream()) }
{
//If chuncked the read chunk by chunk until we hit chunk end symbol }
if (WebSession.Response.IsChunked)
{ /// <summary>
await WebSession.ServerConnection.StreamReader.CopyBytesToStreamChunked(bufferSize, responseBodyStream); /// Read response body as byte[] for current response
} /// </summary>
else private async Task ReadResponseBody()
{ {
if (WebSession.Response.ContentLength > 0) //If not already read (not cached yet)
{ if (WebSession.Response.ResponseBody == null)
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response {
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream, using (var responseBodyStream = new MemoryStream())
WebSession.Response.ContentLength); {
//If chuncked the read chunk by chunk until we hit chunk end symbol
} if (WebSession.Response.IsChunked)
else if (WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0) {
{ await WebSession.ServerConnection.StreamReader.CopyBytesToStreamChunked(bufferSize, responseBodyStream);
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream, long.MaxValue); }
} else
} {
if (WebSession.Response.ContentLength > 0)
WebSession.Response.ResponseBody = await GetDecompressedResponseBody(WebSession.Response.ContentEncoding, {
responseBodyStream.ToArray()); //If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream,
} WebSession.Response.ContentLength);
//set this to true for caching
WebSession.Response.ResponseBodyRead = true; }
} else if ((WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0) || WebSession.Response.ContentLength == -1)
} {
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream, long.MaxValue);
/// <summary> }
/// Gets the request body as bytes }
/// </summary>
/// <returns></returns> WebSession.Response.ResponseBody = await GetDecompressedResponseBody(WebSession.Response.ContentEncoding,
public async Task<byte[]> GetRequestBody() responseBodyStream.ToArray());
{
if (!WebSession.Request.RequestBodyRead) }
{ //set this to true for caching
if (WebSession.Request.RequestLocked) WebSession.Response.ResponseBodyRead = true;
{ }
throw new Exception("You cannot call this function after request is made to server."); }
}
/// <summary>
await ReadRequestBody(); /// Gets the request body as bytes
} /// </summary>
return WebSession.Request.RequestBody; /// <returns></returns>
} public async Task<byte[]> GetRequestBody()
/// <summary> {
/// Gets the request body as string if (!WebSession.Request.RequestBodyRead)
/// </summary> {
/// <returns></returns> if (WebSession.Request.RequestLocked)
public async Task<string> GetRequestBodyAsString() {
{ throw new Exception("You cannot call this function after request is made to server.");
if (!WebSession.Request.RequestBodyRead) }
{
if (WebSession.Request.RequestLocked) await ReadRequestBody();
{ }
throw new Exception("You cannot call this function after request is made to server."); return WebSession.Request.RequestBody;
} }
/// <summary>
await ReadRequestBody(); /// Gets the request body as string
} /// </summary>
//Use the encoding specified in request to decode the byte[] data to string /// <returns></returns>
return WebSession.Request.RequestBodyString ?? (WebSession.Request.RequestBodyString = WebSession.Request.Encoding.GetString(WebSession.Request.RequestBody)); public async Task<string> GetRequestBodyAsString()
} {
if (!WebSession.Request.RequestBodyRead)
/// <summary> {
/// Sets the request body if (WebSession.Request.RequestLocked)
/// </summary> {
/// <param name="body"></param> throw new Exception("You cannot call this function after request is made to server.");
public async Task SetRequestBody(byte[] body) }
{
if (WebSession.Request.RequestLocked) await ReadRequestBody();
{ }
throw new Exception("You cannot call this function after request is made to server."); //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));
}
//syphon out the request body from client before setting the new body
if (!WebSession.Request.RequestBodyRead) /// <summary>
{ /// Sets the request body
await ReadRequestBody(); /// </summary>
} /// <param name="body"></param>
public async Task SetRequestBody(byte[] body)
WebSession.Request.RequestBody = body; {
if (WebSession.Request.RequestLocked)
if (WebSession.Request.IsChunked == false) {
{ throw new Exception("You cannot call this function after request is made to server.");
WebSession.Request.ContentLength = body.Length; }
}
else //syphon out the request body from client before setting the new body
{ if (!WebSession.Request.RequestBodyRead)
WebSession.Request.ContentLength = -1; {
} await ReadRequestBody();
} }
/// <summary> WebSession.Request.RequestBody = body;
/// Sets the body with the specified string
/// </summary> if (WebSession.Request.IsChunked == false)
/// <param name="body"></param> {
public async Task SetRequestBodyString(string body) WebSession.Request.ContentLength = body.Length;
{ }
if (WebSession.Request.RequestLocked) else
{ {
throw new Exception("You cannot call this function after request is made to server."); WebSession.Request.ContentLength = -1;
} }
}
//syphon out the request body from client before setting the new body
if (!WebSession.Request.RequestBodyRead) /// <summary>
{ /// Sets the body with the specified string
await ReadRequestBody(); /// </summary>
} /// <param name="body"></param>
public async Task SetRequestBodyString(string body)
await SetRequestBody(WebSession.Request.Encoding.GetBytes(body)); {
if (WebSession.Request.RequestLocked)
} {
throw new Exception("You cannot call this function after request is made to server.");
/// <summary> }
/// Gets the response body as byte array
/// </summary> //syphon out the request body from client before setting the new body
/// <returns></returns> if (!WebSession.Request.RequestBodyRead)
public async Task<byte[]> GetResponseBody() {
{ await ReadRequestBody();
if (!WebSession.Request.RequestLocked) }
{
throw new Exception("You cannot call this function before request is made to server."); await SetRequestBody(WebSession.Request.Encoding.GetBytes(body));
}
}
await ReadResponseBody();
return WebSession.Response.ResponseBody; /// <summary>
} /// Gets the response body as byte array
/// </summary>
/// <summary> /// <returns></returns>
/// Gets the response body as string public async Task<byte[]> GetResponseBody()
/// </summary> {
/// <returns></returns> if (!WebSession.Request.RequestLocked)
public async Task<string> GetResponseBodyAsString() {
{ throw new Exception("You cannot call this function before request is made to server.");
if (!WebSession.Request.RequestLocked) }
{
throw new Exception("You cannot call this function before request is made to server."); await ReadResponseBody();
} return WebSession.Response.ResponseBody;
}
await GetResponseBody();
/// <summary>
return WebSession.Response.ResponseBodyString ?? /// Gets the response body as string
(WebSession.Response.ResponseBodyString = WebSession.Response.Encoding.GetString(WebSession.Response.ResponseBody)); /// </summary>
} /// <returns></returns>
public async Task<string> GetResponseBodyAsString()
/// <summary> {
/// Set the response body bytes if (!WebSession.Request.RequestLocked)
/// </summary> {
/// <param name="body"></param> throw new Exception("You cannot call this function before request is made to server.");
public async Task SetResponseBody(byte[] body) }
{
if (!WebSession.Request.RequestLocked) await GetResponseBody();
{
throw new Exception("You cannot call this function before request is made to server."); return WebSession.Response.ResponseBodyString ??
} (WebSession.Response.ResponseBodyString = WebSession.Response.Encoding.GetString(WebSession.Response.ResponseBody));
}
//syphon out the response body from server before setting the new body
if (WebSession.Response.ResponseBody == null) /// <summary>
{ /// Set the response body bytes
await GetResponseBody(); /// </summary>
} /// <param name="body"></param>
public async Task SetResponseBody(byte[] body)
WebSession.Response.ResponseBody = body; {
if (!WebSession.Request.RequestLocked)
//If there is a content length header update it {
if (WebSession.Response.IsChunked == false) throw new Exception("You cannot call this function before request is made to server.");
{ }
WebSession.Response.ContentLength = body.Length;
} //syphon out the response body from server before setting the new body
else if (WebSession.Response.ResponseBody == null)
{ {
WebSession.Response.ContentLength = -1; await GetResponseBody();
} }
}
WebSession.Response.ResponseBody = body;
/// <summary>
/// Replace the response body with the specified string //If there is a content length header update it
/// </summary> if (WebSession.Response.IsChunked == false)
/// <param name="body"></param> {
public async Task SetResponseBodyString(string body) WebSession.Response.ContentLength = body.Length;
{ }
if (!WebSession.Request.RequestLocked) else
{ {
throw new Exception("You cannot call this function before request is made to server."); WebSession.Response.ContentLength = -1;
} }
}
//syphon out the response body from server before setting the new body
if (WebSession.Response.ResponseBody == null) /// <summary>
{ /// Replace the response body with the specified string
await GetResponseBody(); /// </summary>
} /// <param name="body"></param>
public async Task SetResponseBodyString(string body)
var bodyBytes = WebSession.Response.Encoding.GetBytes(body); {
if (!WebSession.Request.RequestLocked)
await SetResponseBody(bodyBytes); {
} throw new Exception("You cannot call this function before request is made to server.");
}
private async Task<byte[]> GetDecompressedResponseBody(string encodingType, byte[] responseBodyStream)
{ //syphon out the response body from server before setting the new body
var decompressionFactory = new DecompressionFactory(); if (WebSession.Response.ResponseBody == null)
var decompressor = decompressionFactory.Create(encodingType); {
await GetResponseBody();
return await decompressor.Decompress(responseBodyStream, bufferSize); }
}
var bodyBytes = WebSession.Response.Encoding.GetBytes(body);
/// <summary> await SetResponseBody(bodyBytes);
/// Before request is made to server }
/// Respond with the specified HTML string to client
/// and ignore the request private async Task<byte[]> GetDecompressedResponseBody(string encodingType, byte[] responseBodyStream)
/// </summary> {
/// <param name="html"></param> var decompressionFactory = new DecompressionFactory();
public async Task Ok(string html) var decompressor = decompressionFactory.Create(encodingType);
{
if (WebSession.Request.RequestLocked) return await decompressor.Decompress(responseBodyStream, bufferSize);
{ }
throw new Exception("You cannot call this function after request is made to server.");
} /// <summary>
/// Before request is made to server
if (html == null) /// Respond with the specified HTML string to client
{ /// and ignore the request
html = string.Empty; /// </summary>
} /// <param name="html"></param>
public async Task Ok(string html)
var result = Encoding.Default.GetBytes(html); {
await Ok(html, null);
await Ok(result); }
}
/// <summary>
/// <summary> /// Before request is made to server
/// Before request is made to server /// Respond with the specified HTML string to client
/// Respond with the specified byte[] to client /// and ignore the request
/// and ignore the request /// </summary>
/// </summary> /// <param name="html"></param>
/// <param name="body"></param> /// <param name="headers"></param>
public async Task Ok(byte[] result) public async Task Ok(string html, Dictionary<string, HttpHeader> headers)
{ {
var response = new OkResponse(); if (WebSession.Request.RequestLocked)
{
response.HttpVersion = WebSession.Request.HttpVersion; throw new Exception("You cannot call this function after request is made to server.");
response.ResponseBody = result; }
await Respond(response); if (html == null)
{
WebSession.Request.CancelRequest = true; html = string.Empty;
} }
public async Task Redirect(string url) var result = Encoding.Default.GetBytes(html);
{
var response = new RedirectResponse(); await Ok(result, headers);
}
response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseHeaders.Add("Location", new Models.HttpHeader("Location", url)); /// <summary>
response.ResponseBody = Encoding.ASCII.GetBytes(string.Empty); /// Before request is made to server
/// Respond with the specified byte[] to client
await Respond(response); /// and ignore the request
/// </summary>
WebSession.Request.CancelRequest = true; /// <param name="result"></param>
} public async Task Ok(byte[] result)
{
/// a generic responder method await Ok(result, null);
public async Task Respond(Response response) }
{
WebSession.Request.RequestLocked = true; /// <summary>
/// Before request is made to server
response.ResponseLocked = true; /// Respond with the specified byte[] to client
response.ResponseBodyRead = true; /// and ignore the request
/// </summary>
WebSession.Response = response; /// <param name="result"></param>
/// <param name="headers"></param>
await httpResponseHandler(this); public async Task Ok(byte[] result, Dictionary<string, HttpHeader> headers)
} {
var response = new OkResponse();
/// <summary> if (headers != null && headers.Count > 0)
/// implement any cleanup here {
/// </summary> response.ResponseHeaders = headers;
public void Dispose() }
{ response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseBody = result;
}
} await Respond(response);
WebSession.Request.CancelRequest = true;
}
public async Task Redirect(string url)
{
var response = new RedirectResponse();
response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseHeaders.Add("Location", new Models.HttpHeader("Location", url));
response.ResponseBody = Encoding.ASCII.GetBytes(string.Empty);
await Respond(response);
WebSession.Request.CancelRequest = true;
}
/// a generic responder method
public async Task Respond(Response response)
{
WebSession.Request.RequestLocked = true;
response.ResponseLocked = true;
response.ResponseBodyRead = true;
WebSession.Response = response;
await httpResponseHandler(this);
}
/// <summary>
/// implement any cleanup here
/// </summary>
public void Dispose()
{
}
}
} }
\ No newline at end of file
...@@ -5,7 +5,7 @@ namespace Titanium.Web.Proxy.Exceptions ...@@ -5,7 +5,7 @@ namespace Titanium.Web.Proxy.Exceptions
/// <summary> /// <summary>
/// An expception thrown when body is unexpectedly empty /// An expception thrown when body is unexpectedly empty
/// </summary> /// </summary>
public class BodyNotFoundException : Exception public class BodyNotFoundException : ProxyException
{ {
public BodyNotFoundException(string message) public BodyNotFoundException(string message)
: base(message) : base(message)
......
using System;
using System.Collections.Generic;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Exceptions
{
/// <summary>
/// Proxy authorization exception
/// </summary>
public class ProxyAuthorizationException : ProxyException
{
/// <summary>
/// Instantiate new instance
/// </summary>
/// <param name="message">Exception message</param>
/// <param name="innerException">Inner exception associated to upstream proxy authorization</param>
/// <param name="headers">Http's headers associated</param>
public ProxyAuthorizationException(string message, Exception innerException, IEnumerable<HttpHeader> headers) : base(message, innerException)
{
Headers = headers;
}
/// <summary>
/// Headers associated with the authorization exception
/// </summary>
public IEnumerable<HttpHeader> Headers { get; }
}
}
\ No newline at end of file
using System;
namespace Titanium.Web.Proxy.Exceptions
{
/// <summary>
/// Base class exception associated with this proxy implementation
/// </summary>
public abstract class ProxyException : Exception
{
/// <summary>
/// Instantiate a new instance of this exception - must be invoked by derived classes' constructors
/// </summary>
/// <param name="message">Exception message</param>
protected ProxyException(string message) : base(message)
{
}
/// <summary>
/// Instantiate this exception - must be invoked by derived classes' constructors
/// </summary>
/// <param name="message">Excception message</param>
/// <param name="innerException">Inner exception associated</param>
protected ProxyException(string message, Exception innerException) : base(message, innerException)
{
}
}
}
\ No newline at end of file
using System;
using Titanium.Web.Proxy.EventArguments;
namespace Titanium.Web.Proxy.Exceptions
{
/// <summary>
/// Proxy HTTP exception
/// </summary>
public class ProxyHttpException : ProxyException
{
/// <summary>
/// Instantiate new instance
/// </summary>
/// <param name="message">Message for this exception</param>
/// <param name="innerException">Associated inner exception</param>
/// <param name="sessionEventArgs">Instance of <see cref="EventArguments.SessionEventArgs"/> associated to the exception</param>
public ProxyHttpException(string message, Exception innerException, SessionEventArgs sessionEventArgs) : base(message, innerException)
{
SessionEventArgs = sessionEventArgs;
}
/// <summary>
/// Gets session info associated to the exception
/// </summary>
/// <remarks>
/// This object should not be edited
/// </remarks>
public SessionEventArgs SessionEventArgs { get; }
}
}
\ No newline at end of file
...@@ -29,27 +29,20 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -29,27 +29,20 @@ 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>
/// </summary> /// copies the specified bytes to the stream from the input stream
/// <param name="streamReader"></param> /// </summary>
/// <param name="stream"></param> /// <param name="streamReader"></param>
/// <param name="totalBytesToRead"></param> /// <param name="bufferSize"></param>
/// <returns></returns> /// <param name="stream"></param>
internal static async Task CopyBytesToStream(this CustomBinaryReader streamReader, int bufferSize, Stream stream, long totalBytesToRead) /// <param name="totalBytesToRead"></param>
/// <returns></returns>
internal static async Task CopyBytesToStream(this CustomBinaryReader streamReader, int bufferSize, Stream stream, long totalBytesToRead)
{ {
var totalbytesRead = 0; var totalbytesRead = 0;
long bytesToRead; long bytesToRead = totalBytesToRead < bufferSize ? totalBytesToRead : bufferSize;
if (totalBytesToRead < bufferSize)
{
bytesToRead = totalBytesToRead;
}
else
{
bytesToRead = bufferSize;
}
while (totalbytesRead < totalBytesToRead) while (totalbytesRead < totalBytesToRead)
{ {
...@@ -72,13 +65,14 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -72,13 +65,14 @@ namespace Titanium.Web.Proxy.Extensions
} }
} }
/// <summary> /// <summary>
/// Copies the stream chunked /// Copies the stream chunked
/// </summary> /// </summary>
/// <param name="clientStreamReader"></param> /// <param name="clientStreamReader"></param>
/// <param name="stream"></param> /// <param name="bufferSize"></param>
/// <returns></returns> /// <param name="stream"></param>
internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, int bufferSize, Stream stream) /// <returns></returns>
internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, int bufferSize, Stream stream)
{ {
while (true) while (true)
{ {
...@@ -117,30 +111,32 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -117,30 +111,32 @@ namespace Titanium.Web.Proxy.Extensions
await WriteResponseBodyChunked(data, clientStream); await WriteResponseBodyChunked(data, clientStream);
} }
} }
/// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream /// <summary>
/// optionally chunked /// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// </summary> /// optionally chunked
/// <param name="inStreamReader"></param> /// </summary>
/// <param name="outStream"></param> /// <param name="inStreamReader"></param>
/// <param name="isChunked"></param> /// <param name="bufferSize"></param>
/// <param name="ContentLength"></param> /// <param name="outStream"></param>
/// <returns></returns> /// <param name="isChunked"></param>
internal static async Task WriteResponseBody(this CustomBinaryReader inStreamReader, int bufferSize, Stream outStream, bool isChunked, long ContentLength) /// <param name="contentLength"></param>
/// <returns></returns>
internal static async Task WriteResponseBody(this CustomBinaryReader inStreamReader, int bufferSize, Stream outStream, bool isChunked, long contentLength)
{ {
if (!isChunked) if (!isChunked)
{ {
//http 1.0 //http 1.0
if (ContentLength == -1) if (contentLength == -1)
{ {
ContentLength = long.MaxValue; contentLength = long.MaxValue;
} }
int bytesToRead = bufferSize; int bytesToRead = bufferSize;
if (ContentLength < bufferSize) if (contentLength < bufferSize)
{ {
bytesToRead = (int)ContentLength; bytesToRead = (int)contentLength;
} }
var buffer = new byte[bufferSize]; var buffer = new byte[bufferSize];
...@@ -153,11 +149,11 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -153,11 +149,11 @@ namespace Titanium.Web.Proxy.Extensions
await outStream.WriteAsync(buffer, 0, bytesRead); await outStream.WriteAsync(buffer, 0, bytesRead);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
if (totalBytesRead == ContentLength) if (totalBytesRead == contentLength)
break; break;
bytesRead = 0; bytesRead = 0;
var remainingBytes = (ContentLength - totalBytesRead); var remainingBytes = (contentLength - totalBytesRead);
bytesToRead = remainingBytes > (long)bufferSize ? bufferSize : (int)remainingBytes; bytesToRead = remainingBytes > (long)bufferSize ? bufferSize : (int)remainingBytes;
} }
} }
...@@ -167,13 +163,14 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -167,13 +163,14 @@ namespace Titanium.Web.Proxy.Extensions
} }
} }
/// <summary> /// <summary>
/// Copies the streams chunked /// Copies the streams chunked
/// </summary> /// </summary>
/// <param name="inStreamReader"></param> /// <param name="inStreamReader"></param>
/// <param name="outStream"></param> /// <param name="bufferSize"></param>
/// <returns></returns> /// <param name="outStream"></param>
internal static async Task WriteResponseBodyChunked(this CustomBinaryReader inStreamReader, int bufferSize, Stream outStream) /// <returns></returns>
internal static async Task WriteResponseBodyChunked(this CustomBinaryReader inStreamReader, int bufferSize, Stream outStream)
{ {
while (true) while (true)
{ {
......
...@@ -37,37 +37,30 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -37,37 +37,30 @@ namespace Titanium.Web.Proxy.Helpers
{ {
using (var readBuffer = new MemoryStream()) using (var readBuffer = new MemoryStream())
{ {
try var lastChar = default(char);
{ var buffer = new byte[1];
var lastChar = default(char);
var buffer = new byte[1]; 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 new line {
if (lastChar == '\r' && buffer[0] == '\n') var result = readBuffer.ToArray();
{ return encoding.GetString(result.SubArray(0, result.Length - 1));
var result = readBuffer.ToArray(); }
return encoding.GetString(result.SubArray(0, result.Length - 1)); //end of stream
} if (buffer[0] == '\0')
//end of stream {
if (buffer[0] == '\0') return encoding.GetString(readBuffer.ToArray());
{ }
return encoding.GetString(readBuffer.ToArray());
} await readBuffer.WriteAsync(buffer,0,1);
await readBuffer.WriteAsync(buffer,0,1); //store last char for new line comparison
lastChar = (char)buffer[0];
//store last char for new line comparison }
lastChar = (char)buffer[0];
} return encoding.GetString(readBuffer.ToArray());
return encoding.GetString(readBuffer.ToArray());
}
catch (IOException)
{
throw;
}
} }
} }
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Helpers
{
internal class NetworkHelper
{
private static int FindProcessIdFromLocalPort(int port, IpVersion ipVersion)
{
var tcpRow = TcpHelper.GetExtendedTcpTable(ipVersion).FirstOrDefault(
row => row.LocalEndPoint.Port == port);
return tcpRow?.ProcessId ?? 0;
}
internal static int GetProcessIdFromPort(int port, bool ipV6Enabled)
{
var processId = FindProcessIdFromLocalPort(port, IpVersion.Ipv4);
if (processId > 0 && !ipV6Enabled)
{
return processId;
}
return FindProcessIdFromLocalPort(port, IpVersion.Ipv6);
}
/// <summary>
/// Adapated from below link
/// http://stackoverflow.com/questions/11834091/how-to-check-if-localhost
/// </summary>
/// <param name="address></param>
/// <returns></returns>
internal static bool IsLocalIpAddress(IPAddress address)
{
try
{
// get local IP addresses
IPAddress[] localIPs = Dns.GetHostAddresses(Dns.GetHostName());
// test if any host IP equals to any local IP or to localhost
// is localhost
if (IPAddress.IsLoopback(address)) return true;
// is local address
foreach (IPAddress localIP in localIPs)
{
if (address.Equals(localIP)) return true;
}
}
catch { }
return false;
}
}
}
...@@ -4,14 +4,20 @@ using Microsoft.Win32; ...@@ -4,14 +4,20 @@ using Microsoft.Win32;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Sockets;
/// <summary> /// <summary>
/// Helper classes for setting system proxy settings /// Helper classes for setting system proxy settings
/// </summary> /// </summary>
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
internal enum ProxyProtocolType
internal class NativeMethods {
Http,
Https,
}
internal partial class NativeMethods
{ {
[DllImport("wininet.dll")] [DllImport("wininet.dll")]
internal static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, internal static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer,
...@@ -26,16 +32,10 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -26,16 +32,10 @@ namespace Titanium.Web.Proxy.Helpers
public override string ToString() public override string ToString()
{ {
if (!IsHttps) return $"{(IsHttps ? "https" : "http")}={HostName}:{Port}";
{
return "http=" + HostName + ":" + Port;
}
else
{
return "https=" + HostName + ":" + Port;
}
} }
} }
/// <summary> /// <summary>
/// Manage system proxy settings /// Manage system proxy settings
/// </summary> /// </summary>
...@@ -44,62 +44,17 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -44,62 +44,17 @@ namespace Titanium.Web.Proxy.Helpers
internal const int InternetOptionSettingsChanged = 39; internal const int InternetOptionSettingsChanged = 39;
internal const int InternetOptionRefresh = 37; internal const int InternetOptionRefresh = 37;
internal void SetHttpProxy(string hostname, int port) internal void SetHttpProxy(string hostname, int port)
{ {
var reg = Registry.CurrentUser.OpenSubKey( SetProxy(hostname, port, ProxyProtocolType.Http);
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true);
if (reg != null)
{
prepareRegistry(reg);
var exisitingContent = reg.GetValue("ProxyServer") as string;
var existingSystemProxyValues = GetSystemProxyValues(exisitingContent);
existingSystemProxyValues.RemoveAll(x => !x.IsHttps);
existingSystemProxyValues.Add(new HttpSystemProxyValue()
{
HostName = hostname,
IsHttps = false,
Port = port
});
reg.SetValue("ProxyEnable", 1);
reg.SetValue("ProxyServer", String.Join(";", existingSystemProxyValues.Select(x => x.ToString()).ToArray()));
}
Refresh();
} }
/// <summary> /// <summary>
/// Remove the http proxy setting from current machine /// Remove the http proxy setting from current machine
/// </summary> /// </summary>
internal void RemoveHttpProxy() internal void RemoveHttpProxy()
{ {
var reg = Registry.CurrentUser.OpenSubKey( RemoveProxy(ProxyProtocolType.Http);
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true);
if (reg != null)
{
if (reg.GetValue("ProxyServer") != null)
{
var exisitingContent = reg.GetValue("ProxyServer") as string;
var existingSystemProxyValues = GetSystemProxyValues(exisitingContent);
existingSystemProxyValues.RemoveAll(x => !x.IsHttps);
if (!(existingSystemProxyValues.Count() == 0))
{
reg.SetValue("ProxyEnable", 1);
reg.SetValue("ProxyServer", String.Join(";", existingSystemProxyValues.Select(x => x.ToString()).ToArray()));
}
else
{
reg.SetValue("ProxyEnable", 0);
reg.SetValue("ProxyServer", string.Empty);
}
}
}
Refresh();
} }
/// <summary> /// <summary>
...@@ -107,60 +62,65 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -107,60 +62,65 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary> /// </summary>
/// <param name="hostname"></param> /// <param name="hostname"></param>
/// <param name="port"></param> /// <param name="port"></param>
internal void SetHttpsProxy(string hostname, int port) internal void SetHttpsProxy(string hostname, int port)
{
SetProxy(hostname, port, ProxyProtocolType.Https);
}
/// <summary>
/// Removes the https proxy setting to nothing
/// </summary>
internal void RemoveHttpsProxy()
{
RemoveProxy(ProxyProtocolType.Https);
}
private void SetProxy(string hostname, int port, ProxyProtocolType protocolType)
{ {
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);
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.IsHttps); existingSystemProxyValues.RemoveAll(x => protocolType == ProxyProtocolType.Https ? x.IsHttps : !x.IsHttps);
existingSystemProxyValues.Add(new HttpSystemProxyValue() existingSystemProxyValues.Add(new HttpSystemProxyValue()
{ {
HostName = hostname, HostName = hostname,
IsHttps = true, IsHttps = protocolType == ProxyProtocolType.Https,
Port = port Port = port
}); });
reg.SetValue("ProxyEnable", 1); reg.SetValue("ProxyEnable", 1);
reg.SetValue("ProxyServer", String.Join(";", existingSystemProxyValues.Select(x => x.ToString()).ToArray())); reg.SetValue("ProxyServer", string.Join(";", existingSystemProxyValues.Select(x => x.ToString()).ToArray()));
} }
Refresh(); Refresh();
} }
/// <summary> private void RemoveProxy(ProxyProtocolType protocolType)
/// Removes the https proxy setting to nothing
/// </summary>
internal 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);
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);
existingSystemProxyValues.RemoveAll(x => protocolType == ProxyProtocolType.Https ? x.IsHttps : !x.IsHttps);
var existingSystemProxyValues = GetSystemProxyValues(exisitingContent);
existingSystemProxyValues.RemoveAll(x => x.IsHttps);
if (!(existingSystemProxyValues.Count() == 0))
{
reg.SetValue("ProxyEnable", 1);
reg.SetValue("ProxyServer", String.Join(";", existingSystemProxyValues.Select(x => x.ToString()).ToArray()));
}
else
{
reg.SetValue("ProxyEnable", 0);
reg.SetValue("ProxyServer", string.Empty);
}
if (existingSystemProxyValues.Count != 0)
{
reg.SetValue("ProxyEnable", 1);
reg.SetValue("ProxyServer", string.Join(";", existingSystemProxyValues.Select(x => x.ToString()).ToArray()));
}
else
{
reg.SetValue("ProxyEnable", 0);
reg.SetValue("ProxyServer", string.Empty);
} }
} }
...@@ -170,7 +130,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -170,7 +130,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary> /// <summary>
/// Removes all types of proxy settings (both http & https) /// Removes all types of proxy settings (both http & https)
/// </summary> /// </summary>
internal void DisableAllProxy() internal 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);
...@@ -189,7 +149,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -189,7 +149,7 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary> /// </summary>
/// <param name="prevServerValue"></param> /// <param name="prevServerValue"></param>
/// <returns></returns> /// <returns></returns>
private List<HttpSystemProxyValue> GetSystemProxyValues(string prevServerValue) private List<HttpSystemProxyValue> GetSystemProxyValues(string prevServerValue)
{ {
var result = new List<HttpSystemProxyValue>(); var result = new List<HttpSystemProxyValue>();
...@@ -200,16 +160,11 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -200,16 +160,11 @@ namespace Titanium.Web.Proxy.Helpers
if (proxyValues.Length > 0) if (proxyValues.Length > 0)
{ {
foreach (var value in proxyValues) result.AddRange(proxyValues.Select(ParseProxyValue).Where(parsedValue => parsedValue != null));
{
var parsedValue = parseProxyValue(value);
if (parsedValue != null)
result.Add(parsedValue);
}
} }
else else
{ {
var parsedValue = parseProxyValue(prevServerValue); var parsedValue = ParseProxyValue(prevServerValue);
if (parsedValue != null) if (parsedValue != null)
result.Add(parsedValue); result.Add(parsedValue);
} }
...@@ -222,29 +177,21 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -222,29 +177,21 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary> /// </summary>
/// <param name="value"></param> /// <param name="value"></param>
/// <returns></returns> /// <returns></returns>
private HttpSystemProxyValue parseProxyValue(string value) private HttpSystemProxyValue ParseProxyValue(string value)
{ {
var tmp = Regex.Replace(value, @"\s+", " ").Trim().ToLower(); var tmp = Regex.Replace(value, @"\s+", " ").Trim().ToLower();
if (tmp.StartsWith("http="))
{ if (tmp.StartsWith("http=") || tmp.StartsWith("https="))
var endPoint = tmp.Substring(5);
return new HttpSystemProxyValue()
{
HostName = endPoint.Split(':')[0],
Port = int.Parse(endPoint.Split(':')[1]),
IsHttps = false
};
}
else if (tmp.StartsWith("https="))
{ {
var endPoint = tmp.Substring(5); var endPoint = tmp.Substring(5);
return new HttpSystemProxyValue() return new HttpSystemProxyValue()
{ {
HostName = endPoint.Split(':')[0], HostName = endPoint.Split(':')[0],
Port = int.Parse(endPoint.Split(':')[1]), Port = int.Parse(endPoint.Split(':')[1]),
IsHttps = true IsHttps = tmp.StartsWith("https=")
}; };
} }
return null; return null;
} }
...@@ -252,7 +199,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -252,7 +199,7 @@ namespace Titanium.Web.Proxy.Helpers
/// Prepares the proxy server registry (create empty values if they don't exist) /// Prepares the proxy server registry (create empty values if they don't exist)
/// </summary> /// </summary>
/// <param name="reg"></param> /// <param name="reg"></param>
private void prepareRegistry(RegistryKey reg) private static void PrepareRegistry(RegistryKey reg)
{ {
if (reg.GetValue("ProxyEnable") == null) if (reg.GetValue("ProxyEnable") == null)
{ {
...@@ -269,7 +216,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -269,7 +216,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary> /// <summary>
/// Refresh the settings so that the system know about a change in proxy setting /// Refresh the settings so that the system know about a change in proxy setting
/// </summary> /// </summary>
private void Refresh() private void Refresh()
{ {
NativeMethods.InternetSetOption(IntPtr.Zero, InternetOptionSettingsChanged, IntPtr.Zero, 0); NativeMethods.InternetSetOption(IntPtr.Zero, InternetOptionSettingsChanged, IntPtr.Zero, 0);
NativeMethods.InternetSetOption(IntPtr.Zero, InternetOptionRefresh, IntPtr.Zero, 0); NativeMethods.InternetSetOption(IntPtr.Zero, InternetOptionRefresh, IntPtr.Zero, 0);
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Security; using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Security;
using System.Security.Authentication; using System.Runtime.InteropServices;
using System.Text; using System.Security.Authentication;
using System.Threading.Tasks; using System.Text;
using Titanium.Web.Proxy.Extensions; using System.Threading.Tasks;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.Helpers using Titanium.Web.Proxy.Tcp;
{
namespace Titanium.Web.Proxy.Helpers
internal class TcpHelper {
{ internal enum IpVersion
{
/// <summary> Ipv4 = 1,
/// relays the input clientStream to the server at the specified host name & port with the given httpCmd & headers as prefix Ipv6 = 2,
/// Usefull for websocket requests }
/// </summary>
/// <param name="bufferSize"></param> internal partial class NativeMethods
/// <param name="connectionTimeOutSeconds"></param> {
/// <param name="remoteHostName"></param> internal const int AfInet = 2;
/// <param name="httpCmd"></param> internal const int AfInet6 = 23;
/// <param name="httpVersion"></param>
/// <param name="requestHeaders"></param> internal enum TcpTableType
/// <param name="isHttps"></param> {
/// <param name="remotePort"></param> BasicListener,
/// <param name="supportedProtocols"></param> BasicConnections,
/// <param name="remoteCertificateValidationCallback"></param> BasicAll,
/// <param name="localCertificateSelectionCallback"></param> OwnerPidListener,
/// <param name="clientStream"></param> OwnerPidConnections,
/// <param name="tcpConnectionFactory"></param> OwnerPidAll,
/// <returns></returns> OwnerModuleListener,
internal static async Task SendRaw(int bufferSize, int connectionTimeOutSeconds, OwnerModuleConnections,
string remoteHostName, int remotePort, string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders, OwnerModuleAll,
bool isHttps, SslProtocols supportedProtocols, }
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
Stream clientStream, TcpConnectionFactory tcpConnectionFactory) /// <summary>
{ /// <see cref="http://msdn2.microsoft.com/en-us/library/aa366921.aspx"/>
//prepare the prefix content /// </summary>
StringBuilder sb = null; [StructLayout(LayoutKind.Sequential)]
if (httpCmd != null || requestHeaders != null) internal struct TcpTable
{ {
sb = new StringBuilder(); public uint length;
public TcpRow row;
if (httpCmd != null) }
{
sb.Append(httpCmd); /// <summary>
sb.Append(Environment.NewLine); /// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
} /// </summary>
[StructLayout(LayoutKind.Sequential)]
if (requestHeaders != null) internal struct TcpRow
{ {
foreach (var header in requestHeaders.Select(t => t.Value.ToString())) public TcpState state;
{ public uint localAddr;
sb.Append(header); public byte localPort1;
sb.Append(Environment.NewLine); public byte localPort2;
} public byte localPort3;
} public byte localPort4;
public uint remoteAddr;
sb.Append(Environment.NewLine); public byte remotePort1;
} public byte remotePort2;
public byte remotePort3;
var tcpConnection = await tcpConnectionFactory.CreateClient(bufferSize, connectionTimeOutSeconds, public byte remotePort4;
remoteHostName, remotePort, public int owningPid;
httpVersion, isHttps, }
supportedProtocols, remoteCertificateValidationCallback, localCertificateSelectionCallback,
null, null, clientStream); /// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/>
try /// </summary>
{ [DllImport("iphlpapi.dll", SetLastError = true)]
TcpClient tunnelClient = tcpConnection.TcpClient; internal static extern uint GetExtendedTcpTable(IntPtr tcpTable, ref int size, bool sort, int ipVersion, int tableClass, int reserved);
}
Stream tunnelStream = tcpConnection.Stream;
internal class TcpHelper
Task sendRelay; {
/// <summary>
//Now async relay all server=>client & client=>server data /// Gets the extended TCP table.
if (sb != null) /// </summary>
{ /// <returns>Collection of <see cref="TcpRow"/>.</returns>
sendRelay = clientStream.CopyToAsync(sb.ToString(), tunnelStream); internal static TcpTable GetExtendedTcpTable(IpVersion ipVersion)
} {
else List<TcpRow> tcpRows = new List<TcpRow>();
{
sendRelay = clientStream.CopyToAsync(string.Empty, tunnelStream); IntPtr tcpTable = IntPtr.Zero;
} int tcpTableLength = 0;
var ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6;
var receiveRelay = tunnelStream.CopyToAsync(string.Empty, clientStream);
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) != 0)
await Task.WhenAll(sendRelay, receiveRelay); {
} try
catch {
{ tcpTable = Marshal.AllocHGlobal(tcpTableLength);
throw; if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) == 0)
} {
finally NativeMethods.TcpTable table = (NativeMethods.TcpTable)Marshal.PtrToStructure(tcpTable, typeof(NativeMethods.TcpTable));
{
tcpConnection.Dispose(); IntPtr rowPtr = (IntPtr)((long)tcpTable + Marshal.SizeOf(table.length));
}
} for (int i = 0; i < table.length; ++i)
{
} tcpRows.Add(new TcpRow((NativeMethods.TcpRow)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow))));
rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow)));
}
}
}
finally
{
if (tcpTable != IntPtr.Zero)
{
Marshal.FreeHGlobal(tcpTable);
}
}
}
return new TcpTable(tcpRows);
}
/// <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="bufferSize"></param>
/// <param name="connectionTimeOutSeconds"></param>
/// <param name="remoteHostName"></param>
/// <param name="httpCmd"></param>
/// <param name="httpVersion"></param>
/// <param name="requestHeaders"></param>
/// <param name="isHttps"></param>
/// <param name="remotePort"></param>
/// <param name="supportedProtocols"></param>
/// <param name="remoteCertificateValidationCallback"></param>
/// <param name="localCertificateSelectionCallback"></param>
/// <param name="clientStream"></param>
/// <param name="tcpConnectionFactory"></param>
/// <returns></returns>
internal static async Task SendRaw(int bufferSize, int connectionTimeOutSeconds,
string remoteHostName, int remotePort, string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders,
bool isHttps, SslProtocols supportedProtocols,
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
Stream clientStream, TcpConnectionFactory tcpConnectionFactory)
{
//prepare the prefix content
StringBuilder sb = null;
if (httpCmd != null || requestHeaders != null)
{
sb = new StringBuilder();
if (httpCmd != null)
{
sb.Append(httpCmd);
sb.Append(Environment.NewLine);
}
if (requestHeaders != null)
{
foreach (var header in requestHeaders.Select(t => t.Value.ToString()))
{
sb.Append(header);
sb.Append(Environment.NewLine);
}
}
sb.Append(Environment.NewLine);
}
var tcpConnection = await tcpConnectionFactory.CreateClient(bufferSize, connectionTimeOutSeconds,
remoteHostName, remotePort,
httpVersion, isHttps,
supportedProtocols, remoteCertificateValidationCallback, localCertificateSelectionCallback,
null, null, clientStream);
try
{
Stream tunnelStream = tcpConnection.Stream;
//Now async relay all server=>client & client=>server data
var sendRelay = clientStream.CopyToAsync(sb?.ToString() ?? string.Empty, tunnelStream);
var receiveRelay = tunnelStream.CopyToAsync(string.Empty, clientStream);
await Task.WhenAll(sendRelay, receiveRelay);
}
finally
{
tcpConnection.Dispose();
}
}
}
} }
\ No newline at end of file
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
/// <summary> /// <summary>
/// Used to communicate with the server over HTTP(S) /// Used to communicate with the server over HTTP(S)
/// </summary> /// </summary>
public class HttpWebClient public class HttpWebClient
{ {
/// <summary>
/// Connection to server
/// </summary> /// <summary>
internal TcpConnection ServerConnection { get; set; } /// Connection to server
/// </summary>
public Request Request { get; set; } internal TcpConnection ServerConnection { get; set; }
public Response Response { get; set; }
public Guid RequestId { get; private set; }
/// <summary>
/// Is Https? public List<HttpHeader> ConnectHeaders { get; set; }
/// </summary> public Request Request { get; set; }
public bool IsHttps public Response Response { get; set; }
{
get /// <summary>
{ /// PID of the process that is created the current session when client is running in this machine
return this.Request.RequestUri.Scheme == Uri.UriSchemeHttps; /// If client is remote then this will return
} /// </summary>
} public Lazy<int> ProcessId { get; internal set; }
/// <summary> /// <summary>
/// Set the tcp connection to server used by this webclient /// Is Https?
/// </summary> /// </summary>
/// <param name="Connection"></param> public bool IsHttps => this.Request.RequestUri.Scheme == Uri.UriSchemeHttps;
internal void SetConnection(TcpConnection Connection)
{
ServerConnection = Connection; internal HttpWebClient()
} {
this.RequestId = Guid.NewGuid();
internal HttpWebClient()
{ this.Request = new Request();
this.Request = new Request(); this.Response = new Response();
this.Response = new Response(); }
}
/// <summary>
/// <summary> /// Set the tcp connection to server used by this webclient
/// Prepare & send the http(s) request /// </summary>
/// </summary> /// <param name="connection">Instance of <see cref="TcpConnection"/></param>
/// <returns></returns> internal void SetConnection(TcpConnection connection)
internal async Task SendRequest(bool enable100ContinueBehaviour) {
{ connection.LastAccess = DateTime.Now;
Stream stream = ServerConnection.Stream; ServerConnection = connection;
}
StringBuilder requestLines = new StringBuilder();
//prepare the request & headers /// <summary>
requestLines.AppendLine(string.Join(" ", new string[3] /// Prepare & send the http(s) request
{ /// </summary>
this.Request.Method, /// <returns></returns>
this.Request.RequestUri.PathAndQuery, internal async Task SendRequest(bool enable100ContinueBehaviour)
string.Format("HTTP/{0}.{1}",this.Request.HttpVersion.Major, this.Request.HttpVersion.Minor) {
})); Stream stream = ServerConnection.Stream;
//write request headers StringBuilder requestLines = new StringBuilder();
foreach (var headerItem in this.Request.RequestHeaders)
{ //prepare the request & headers
var header = headerItem.Value; if ((ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false) || (ServerConnection.UpStreamHttpsProxy != null && ServerConnection.IsHttps == true))
requestLines.AppendLine(header.Name + ':' + header.Value); {
} requestLines.AppendLine(string.Join(" ", this.Request.Method, this.Request.RequestUri.AbsoluteUri, $"HTTP/{this.Request.HttpVersion.Major}.{this.Request.HttpVersion.Minor}"));
}
//write non unique request headers else
foreach (var headerItem in this.Request.NonUniqueRequestHeaders) {
{ requestLines.AppendLine(string.Join(" ", this.Request.Method, this.Request.RequestUri.PathAndQuery, $"HTTP/{this.Request.HttpVersion.Major}.{this.Request.HttpVersion.Minor}"));
var headers = headerItem.Value; }
foreach (var header in headers)
{ //Send Authentication to Upstream proxy if needed
requestLines.AppendLine(header.Name + ':' + header.Value); if (ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false && !string.IsNullOrEmpty(ServerConnection.UpStreamHttpProxy.UserName) && ServerConnection.UpStreamHttpProxy.Password != null)
} {
} requestLines.AppendLine("Proxy-Connection: keep-alive");
requestLines.AppendLine("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(ServerConnection.UpStreamHttpProxy.UserName + ":" + ServerConnection.UpStreamHttpProxy.Password)));
requestLines.AppendLine(); }
//write request headers
string request = requestLines.ToString(); foreach (var headerItem in this.Request.RequestHeaders)
byte[] requestBytes = Encoding.ASCII.GetBytes(request); {
await stream.WriteAsync(requestBytes, 0, requestBytes.Length); var header = headerItem.Value;
await stream.FlushAsync(); if (headerItem.Key != "Proxy-Authorization")
{
if (enable100ContinueBehaviour) requestLines.AppendLine(header.Name + ':' + header.Value);
{ }
if (this.Request.ExpectContinue) }
{
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3); //write non unique request headers
var responseStatusCode = httpResult[1].Trim(); foreach (var headerItem in this.Request.NonUniqueRequestHeaders)
var responseStatusDescription = httpResult[2].Trim(); {
var headers = headerItem.Value;
//find if server is willing for expect continue foreach (var header in headers)
if (responseStatusCode.Equals("100") {
&& responseStatusDescription.ToLower().Equals("continue")) if (headerItem.Key != "Proxy-Authorization")
{ {
this.Request.Is100Continue = true; requestLines.AppendLine(header.Name + ':' + header.Value);
await ServerConnection.StreamReader.ReadLineAsync(); }
} }
else if (responseStatusCode.Equals("417") }
&& responseStatusDescription.ToLower().Equals("expectation failed"))
{ requestLines.AppendLine();
this.Request.ExpectationFailed = true;
await ServerConnection.StreamReader.ReadLineAsync(); string request = requestLines.ToString();
} byte[] requestBytes = Encoding.ASCII.GetBytes(request);
}
} await stream.WriteAsync(requestBytes, 0, requestBytes.Length);
} await stream.FlushAsync();
/// <summary> if (enable100ContinueBehaviour)
/// Receive & parse the http response from server {
/// </summary> if (this.Request.ExpectContinue)
/// <returns></returns> {
internal async Task ReceiveResponse() var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
{ var responseStatusCode = httpResult[1].Trim();
//return if this is already read var responseStatusDescription = httpResult[2].Trim();
if (this.Response.ResponseStatusCode != null) return;
//find if server is willing for expect continue
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3); if (responseStatusCode.Equals("100")
&& responseStatusDescription.ToLower().Equals("continue"))
if (string.IsNullOrEmpty(httpResult[0])) {
{ this.Request.Is100Continue = true;
//Empty content in first-line, try again await ServerConnection.StreamReader.ReadLineAsync();
httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3); }
} else if (responseStatusCode.Equals("417")
&& responseStatusDescription.ToLower().Equals("expectation failed"))
var httpVersion = httpResult[0].Trim().ToLower(); {
this.Request.ExpectationFailed = true;
var version = new Version(1, 1); await ServerConnection.StreamReader.ReadLineAsync();
if (httpVersion == "http/1.0") }
{ }
version = new Version(1, 0); }
} }
this.Response.HttpVersion = version; /// <summary>
this.Response.ResponseStatusCode = httpResult[1].Trim(); /// Receive & parse the http response from server
this.Response.ResponseStatusDescription = httpResult[2].Trim(); /// </summary>
/// <returns></returns>
//For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request internal async Task ReceiveResponse()
if (this.Response.ResponseStatusCode.Equals("100") {
&& this.Response.ResponseStatusDescription.ToLower().Equals("continue")) //return if this is already read
{ if (this.Response.ResponseStatusCode != null) return;
//Read the next line after 100-continue
this.Response.Is100Continue = true; var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
this.Response.ResponseStatusCode = null;
await ServerConnection.StreamReader.ReadLineAsync(); if (string.IsNullOrEmpty(httpResult[0]))
//now receive response {
await ReceiveResponse(); //Empty content in first-line, try again
return; httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
} }
else if (this.Response.ResponseStatusCode.Equals("417")
&& this.Response.ResponseStatusDescription.ToLower().Equals("expectation failed")) var httpVersion = httpResult[0].Trim().ToLower();
{
//read next line after expectation failed response var version = new Version(1, 1);
this.Response.ExpectationFailed = true; if (httpVersion == "http/1.0")
this.Response.ResponseStatusCode = null; {
await ServerConnection.StreamReader.ReadLineAsync(); version = new Version(1, 0);
//now receive response }
await ReceiveResponse();
return; this.Response.HttpVersion = version;
} this.Response.ResponseStatusCode = httpResult[1].Trim();
this.Response.ResponseStatusDescription = httpResult[2].Trim();
//Read the Response headers
//Read the response headers in to unique and non-unique header collections //For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request
string tmpLine; if (this.Response.ResponseStatusCode.Equals("100")
while (!string.IsNullOrEmpty(tmpLine = await ServerConnection.StreamReader.ReadLineAsync())) && this.Response.ResponseStatusDescription.ToLower().Equals("continue"))
{ {
var header = tmpLine.Split(ProxyConstants.ColonSplit, 2); //Read the next line after 100-continue
this.Response.Is100Continue = true;
var newHeader = new HttpHeader(header[0], header[1]); this.Response.ResponseStatusCode = null;
await ServerConnection.StreamReader.ReadLineAsync();
//if header exist in non-unique header collection add it there //now receive response
if (Response.NonUniqueResponseHeaders.ContainsKey(newHeader.Name)) await ReceiveResponse();
{ return;
Response.NonUniqueResponseHeaders[newHeader.Name].Add(newHeader); }
} else if (this.Response.ResponseStatusCode.Equals("417")
//if header is alread in unique header collection then move both to non-unique collection && this.Response.ResponseStatusDescription.ToLower().Equals("expectation failed"))
else if (Response.ResponseHeaders.ContainsKey(newHeader.Name)) {
{ //read next line after expectation failed response
var existing = Response.ResponseHeaders[newHeader.Name]; this.Response.ExpectationFailed = true;
this.Response.ResponseStatusCode = null;
var nonUniqueHeaders = new List<HttpHeader>(); await ServerConnection.StreamReader.ReadLineAsync();
//now receive response
nonUniqueHeaders.Add(existing); await ReceiveResponse();
nonUniqueHeaders.Add(newHeader); return;
}
Response.NonUniqueResponseHeaders.Add(newHeader.Name, nonUniqueHeaders);
Response.ResponseHeaders.Remove(newHeader.Name); //Read the Response headers
} //Read the response headers in to unique and non-unique header collections
//add to unique header collection string tmpLine;
else while (!string.IsNullOrEmpty(tmpLine = await ServerConnection.StreamReader.ReadLineAsync()))
{ {
Response.ResponseHeaders.Add(newHeader.Name, newHeader); var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);
}
} var newHeader = new HttpHeader(header[0], header[1]);
}
} //if header exist in non-unique header collection add it there
if (Response.NonUniqueResponseHeaders.ContainsKey(newHeader.Name))
} {
Response.NonUniqueResponseHeaders[newHeader.Name].Add(newHeader);
}
//if header is alread in unique header collection then move both to non-unique collection
else if (Response.ResponseHeaders.ContainsKey(newHeader.Name))
{
var existing = Response.ResponseHeaders[newHeader.Name];
var nonUniqueHeaders = new List<HttpHeader> {existing, newHeader};
Response.NonUniqueResponseHeaders.Add(newHeader.Name, nonUniqueHeaders);
Response.ResponseHeaders.Remove(newHeader.Name);
}
//add to unique header collection
else
{
Response.ResponseHeaders.Add(newHeader.Name, newHeader);
}
}
}
}
}
\ No newline at end of file
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
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;
...@@ -234,13 +233,14 @@ namespace Titanium.Web.Proxy.Http ...@@ -234,13 +233,14 @@ namespace Titanium.Web.Proxy.Http
/// <summary> /// <summary>
/// Request Url /// Request Url
/// </summary> /// </summary>
public string Url { get { return RequestUri.OriginalString; } } public string Url => RequestUri.OriginalString;
/// <summary> /// <summary>
/// Encoding for this request /// Encoding for this request
/// </summary> /// </summary>
internal Encoding Encoding { get { return this.GetEncoding(); } } internal Encoding Encoding => 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; }
......
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
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;
...@@ -16,9 +15,9 @@ namespace Titanium.Web.Proxy.Http ...@@ -16,9 +15,9 @@ namespace Titanium.Web.Proxy.Http
public string ResponseStatusCode { get; set; } public string ResponseStatusCode { get; set; }
public string ResponseStatusDescription { get; set; } public string ResponseStatusDescription { get; set; }
internal Encoding Encoding { get { return this.GetResponseCharacterEncoding(); } } internal Encoding Encoding => this.GetResponseCharacterEncoding();
/// <summary> /// <summary>
/// Content encoding for this response /// Content encoding for this response
/// </summary> /// </summary>
internal string ContentEncoding internal string ContentEncoding
...@@ -205,7 +204,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -205,7 +204,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary> /// <summary>
/// Response network stream /// Response network stream
/// </summary> /// </summary>
internal Stream ResponseStream { get; set; } public Stream ResponseStream { get; set; }
/// <summary> /// <summary>
/// response body contenst as byte array /// response body contenst as byte array
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
/// <summary> /// <summary>
/// 200 Ok response /// 200 Ok response
/// </summary> /// </summary>
public class OkResponse : Response public sealed class OkResponse : Response
{ {
public OkResponse() public OkResponse()
{ {
......
using Titanium.Web.Proxy.Network; namespace Titanium.Web.Proxy.Http.Responses
namespace Titanium.Web.Proxy.Http.Responses
{ {
/// <summary> /// <summary>
/// Redirect response /// Redirect response
/// </summary> /// </summary>
public class RedirectResponse : Response public sealed class RedirectResponse : Response
{ {
public RedirectResponse() public RedirectResponse()
{ {
......
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
namespace Titanium.Web.Proxy.Models namespace Titanium.Web.Proxy.Models
{ {
/// <summary> /// <summary>
/// An abstract endpoint where the proxy listens /// An abstract endpoint where the proxy listens
/// </summary> /// </summary>
public abstract class ProxyEndPoint public abstract class ProxyEndPoint
{ {
public ProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl) public ProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
{ {
this.IpAddress = IpAddress; this.IpAddress = IpAddress;
this.Port = Port; this.Port = Port;
this.EnableSsl = EnableSsl; this.EnableSsl = EnableSsl;
} }
public IPAddress IpAddress { get; internal set; } public IPAddress IpAddress { get; internal set; }
public int Port { get; internal set; } public int Port { get; internal set; }
public bool EnableSsl { get; internal set; } public bool EnableSsl { get; internal set; }
internal TcpListener listener { get; set; } public bool IpV6Enabled => IpAddress == IPAddress.IPv6Any
} || IpAddress == IPAddress.IPv6Loopback
|| IpAddress == IPAddress.IPv6None;
/// <summary>
/// A proxy endpoint that the client is aware of internal TcpListener listener { get; set; }
/// So client application know that it is communicating with a proxy server }
/// </summary>
public class ExplicitProxyEndPoint : ProxyEndPoint /// <summary>
{ /// A proxy endpoint that the client is aware of
internal bool IsSystemHttpProxy { get; set; } /// So client application know that it is communicating with a proxy server
internal bool IsSystemHttpsProxy { get; set; } /// </summary>
public class ExplicitProxyEndPoint : ProxyEndPoint
public List<string> ExcludedHttpsHostNameRegex { get; set; } {
internal bool IsSystemHttpProxy { get; set; }
public ExplicitProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl) internal bool IsSystemHttpsProxy { get; set; }
: base(IpAddress, Port, EnableSsl)
{ public List<string> ExcludedHttpsHostNameRegex { get; set; }
} public ExplicitProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
} : base(IpAddress, Port, EnableSsl)
{
/// <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 /// <summary>
{ /// A proxy end point client is not aware of
//Name of the Certificate need to be sent (same as the hostname we want to proxy) /// Usefull when requests are redirected to this proxy end point through port forwarding
//This is valid only when UseServerNameIndication is set to false /// </summary>
public string GenericCertificateName { get; set; } public class TransparentProxyEndPoint : ProxyEndPoint
{
//Name of the Certificate need to be sent (same as the hostname we want to proxy)
// public bool UseServerNameIndication { get; set; } //This is valid only when UseServerNameIndication is set to false
public string GenericCertificateName { get; set; }
public TransparentProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
: base(IpAddress, Port, EnableSsl)
{ // public bool UseServerNameIndication { get; set; }
this.GenericCertificateName = "localhost";
} public TransparentProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
} : base(IpAddress, Port, EnableSsl)
{
} this.GenericCertificateName = "localhost";
}
}
}
namespace Titanium.Web.Proxy.Models using System;
{ using System.Net;
/// <summary>
/// An upstream proxy this proxy uses if any namespace Titanium.Web.Proxy.Models
/// </summary> {
public class ExternalProxy /// <summary>
{ /// An upstream proxy this proxy uses if any
public string HostName { get; set; } /// </summary>
public int Port { get; set; } public class ExternalProxy
} {
} private static readonly Lazy<NetworkCredential> DefaultCredentials = new Lazy<NetworkCredential>(() => CredentialCache.DefaultNetworkCredentials);
private string userName;
private string password;
public bool UseDefaultCredentials { get; set; }
public string UserName {
get { return UseDefaultCredentials ? DefaultCredentials.Value.UserName : userName; }
set
{
userName = value;
if (DefaultCredentials.Value.UserName != userName)
{
UseDefaultCredentials = false;
}
}
}
public string Password
{
get { return UseDefaultCredentials ? DefaultCredentials.Value.Password : password; }
set
{
password = value;
if (DefaultCredentials.Value.Password != password)
{
UseDefaultCredentials = false;
}
}
}
public string HostName { get; set; }
public int Port { get; set; }
}
}
using System;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
namespace Titanium.Web.Proxy.Network
{
public class CertificateMaker
{
private Type typeX500DN;
private Type typeX509PrivateKey;
private Type typeOID;
private Type typeOIDS;
private Type typeKUExt;
private Type typeEKUExt;
private Type typeRequestCert;
private Type typeX509Extensions;
private Type typeBasicConstraints;
private Type typeSignerCertificate;
private Type typeX509Enrollment;
private Type typeAlternativeName;
private Type typeAlternativeNames;
private Type typeAlternativeNamesExt;
private string sProviderName = "Microsoft Enhanced Cryptographic Provider v1.0";
private object _SharedPrivateKey;
public CertificateMaker()
{
this.typeX500DN = Type.GetTypeFromProgID("X509Enrollment.CX500DistinguishedName", true);
this.typeX509PrivateKey = Type.GetTypeFromProgID("X509Enrollment.CX509PrivateKey", true);
this.typeOID = Type.GetTypeFromProgID("X509Enrollment.CObjectId", true);
this.typeOIDS = Type.GetTypeFromProgID("X509Enrollment.CObjectIds.1", true);
this.typeEKUExt = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionEnhancedKeyUsage");
this.typeKUExt = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionKeyUsage");
this.typeRequestCert = Type.GetTypeFromProgID("X509Enrollment.CX509CertificateRequestCertificate");
this.typeX509Extensions = Type.GetTypeFromProgID("X509Enrollment.CX509Extensions");
this.typeBasicConstraints = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionBasicConstraints");
this.typeSignerCertificate = Type.GetTypeFromProgID("X509Enrollment.CSignerCertificate");
this.typeX509Enrollment = Type.GetTypeFromProgID("X509Enrollment.CX509Enrollment");
this.typeAlternativeName = Type.GetTypeFromProgID("X509Enrollment.CAlternativeName");
this.typeAlternativeNames = Type.GetTypeFromProgID("X509Enrollment.CAlternativeNames");
this.typeAlternativeNamesExt = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionAlternativeNames");
}
public X509Certificate2 MakeCertificate(string sSubjectCN, bool isRoot,X509Certificate2 signingCert=null)
{
return this.MakeCertificateInternal(sSubjectCN, isRoot, true, signingCert);
}
private X509Certificate2 MakeCertificate(bool IsRoot, string SubjectCN, string FullSubject, int PrivateKeyLength, string HashAlg, DateTime ValidFrom, DateTime ValidTo, X509Certificate2 SigningCertificate)
{
X509Certificate2 cert;
if (IsRoot != (null == SigningCertificate))
{
throw new ArgumentException("You must specify a Signing Certificate if and only if you are not creating a root.", "oSigningCertificate");
}
object x500DN = Activator.CreateInstance(this.typeX500DN);
object[] subject = new object[] { FullSubject, 0 };
this.typeX500DN.InvokeMember("Encode", BindingFlags.InvokeMethod, null, x500DN, subject);
object x500DN2 = Activator.CreateInstance(this.typeX500DN);
if (!IsRoot)
{
subject[0] = SigningCertificate.Subject;
}
this.typeX500DN.InvokeMember("Encode", BindingFlags.InvokeMethod, null, x500DN2, subject);
object sharedPrivateKey = null;
if (!IsRoot)
{
sharedPrivateKey = this._SharedPrivateKey;
}
if (sharedPrivateKey == null)
{
sharedPrivateKey = Activator.CreateInstance(this.typeX509PrivateKey);
subject = new object[] { this.sProviderName };
this.typeX509PrivateKey.InvokeMember("ProviderName", BindingFlags.PutDispProperty, null, sharedPrivateKey, subject);
subject[0] = 2;
this.typeX509PrivateKey.InvokeMember("ExportPolicy", BindingFlags.PutDispProperty, null, sharedPrivateKey, subject);
subject = new object[] { (IsRoot ? 2 : 1) };
this.typeX509PrivateKey.InvokeMember("KeySpec", BindingFlags.PutDispProperty, null, sharedPrivateKey, subject);
if (!IsRoot)
{
subject = new object[] { 176 };
this.typeX509PrivateKey.InvokeMember("KeyUsage", BindingFlags.PutDispProperty, null, sharedPrivateKey, subject);
}
subject[0] = PrivateKeyLength;
this.typeX509PrivateKey.InvokeMember("Length", BindingFlags.PutDispProperty, null, sharedPrivateKey, subject);
this.typeX509PrivateKey.InvokeMember("Create", BindingFlags.InvokeMethod, null, sharedPrivateKey, null);
if (!IsRoot)
{
this._SharedPrivateKey = sharedPrivateKey;
}
}
subject = new object[1];
object obj3 = Activator.CreateInstance(this.typeOID);
subject[0] = "1.3.6.1.5.5.7.3.1";
this.typeOID.InvokeMember("InitializeFromValue", BindingFlags.InvokeMethod, null, obj3, subject);
object obj4 = Activator.CreateInstance(this.typeOIDS);
subject[0] = obj3;
this.typeOIDS.InvokeMember("Add", BindingFlags.InvokeMethod, null, obj4, subject);
object obj5 = Activator.CreateInstance(this.typeEKUExt);
subject[0] = obj4;
this.typeEKUExt.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, obj5, subject);
object obj6 = Activator.CreateInstance(this.typeRequestCert);
subject = new object[] { 1, sharedPrivateKey, string.Empty };
this.typeRequestCert.InvokeMember("InitializeFromPrivateKey", BindingFlags.InvokeMethod, null, obj6, subject);
subject = new object[] { x500DN };
this.typeRequestCert.InvokeMember("Subject", BindingFlags.PutDispProperty, null, obj6, subject);
subject[0] = x500DN;
this.typeRequestCert.InvokeMember("Issuer", BindingFlags.PutDispProperty, null, obj6, subject);
subject[0] = ValidFrom;
this.typeRequestCert.InvokeMember("NotBefore", BindingFlags.PutDispProperty, null, obj6, subject);
subject[0] = ValidTo;
this.typeRequestCert.InvokeMember("NotAfter", BindingFlags.PutDispProperty, null, obj6, subject);
object obj7 = Activator.CreateInstance(this.typeKUExt);
subject[0] = 176;
this.typeKUExt.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, obj7, subject);
object obj8 = this.typeRequestCert.InvokeMember("X509Extensions", BindingFlags.GetProperty, null, obj6, null);
subject = new object[1];
if (!IsRoot)
{
subject[0] = obj7;
this.typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, obj8, subject);
}
subject[0] = obj5;
this.typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, obj8, subject);
if (!IsRoot)
{
object obj12 = Activator.CreateInstance(this.typeSignerCertificate);
subject = new object[] { 0, 0, 12, SigningCertificate.Thumbprint };
this.typeSignerCertificate.InvokeMember("Initialize", BindingFlags.InvokeMethod, null, obj12, subject);
subject = new object[] { obj12 };
this.typeRequestCert.InvokeMember("SignerCertificate", BindingFlags.PutDispProperty, null, obj6, subject);
}
else
{
object obj13 = Activator.CreateInstance(this.typeBasicConstraints);
subject = new object[] { "true", "0" };
this.typeBasicConstraints.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, obj13, subject);
subject = new object[] { obj13 };
this.typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, obj8, subject);
}
object obj14 = Activator.CreateInstance(this.typeOID);
subject = new object[] { 1, 0, 0, HashAlg };
this.typeOID.InvokeMember("InitializeFromAlgorithmName", BindingFlags.InvokeMethod, null, obj14, subject);
subject = new object[] { obj14 };
this.typeRequestCert.InvokeMember("HashAlgorithm", BindingFlags.PutDispProperty, null, obj6, subject);
this.typeRequestCert.InvokeMember("Encode", BindingFlags.InvokeMethod, null, obj6, null);
object obj15 = Activator.CreateInstance(this.typeX509Enrollment);
subject[0] = obj6;
this.typeX509Enrollment.InvokeMember("InitializeFromRequest", BindingFlags.InvokeMethod, null, obj15, subject);
if (IsRoot)
{
subject[0] = "DO_NOT_TRUST_TitaniumProxy-CE";
this.typeX509Enrollment.InvokeMember("CertificateFriendlyName", BindingFlags.PutDispProperty, null, obj15, subject);
}
subject[0] = 0;
object obj16 = this.typeX509Enrollment.InvokeMember("CreateRequest", BindingFlags.InvokeMethod, null, obj15, subject);
subject = new object[] { 2, obj16, 0, string.Empty };
this.typeX509Enrollment.InvokeMember("InstallResponse", BindingFlags.InvokeMethod, null, obj15, subject);
subject = new object[] { null, 0, 1 };
string empty = string.Empty;
try
{
empty = (string)this.typeX509Enrollment.InvokeMember("CreatePFX", BindingFlags.InvokeMethod, null, obj15, subject);
return new X509Certificate2(Convert.FromBase64String(empty), string.Empty, X509KeyStorageFlags.Exportable);
}
catch (Exception exception1)
{
Exception exception = exception1;
cert = null;
}
return cert;
}
private X509Certificate2 MakeCertificateInternal(string sSubjectCN, bool isRoot, bool switchToMTAIfNeeded,X509Certificate2 signingCert=null)
{
X509Certificate2 rCert=null;
if (switchToMTAIfNeeded && Thread.CurrentThread.GetApartmentState() != ApartmentState.MTA)
{
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem((object o) =>
{
rCert = this.MakeCertificateInternal(sSubjectCN, isRoot, false,signingCert);
manualResetEvent.Set();
});
manualResetEvent.WaitOne();
manualResetEvent.Close();
return rCert;
}
string fullSubject = string.Format("CN={0}{1}", sSubjectCN, "");//Subject
string HashAlgo = "SHA256"; //Sig Algo
int GraceDays = -366; //Grace Days
int ValidDays = 1825; //ValiDays
int keyLength = 2048; //KeyLength
DateTime graceTime = DateTime.Now.AddDays((double)GraceDays);
DateTime now = DateTime.Now;
try
{
if (!isRoot)
{
rCert = this.MakeCertificate(false, sSubjectCN, fullSubject, keyLength, HashAlgo, graceTime, now.AddDays((double)ValidDays), signingCert);
}
else
{
rCert = this.MakeCertificate(true, sSubjectCN, fullSubject, keyLength, HashAlgo, graceTime, now.AddDays((double)ValidDays), null);
}
}
catch (Exception e)
{
throw e;
}
return rCert;
}
}
}
using System; using System;
using System.IO;
using System.Diagnostics;
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading;
using System.Linq; using System.Linq;
using System.Collections.Concurrent;
using System.IO;
namespace Titanium.Web.Proxy.Network namespace Titanium.Web.Proxy.Network
{ {
...@@ -15,74 +13,86 @@ namespace Titanium.Web.Proxy.Network ...@@ -15,74 +13,86 @@ namespace Titanium.Web.Proxy.Network
/// </summary> /// </summary>
internal class CertificateManager : IDisposable internal class CertificateManager : IDisposable
{ {
private const string CertCreateFormat = private CertificateMaker certEngine = null;
"-ss {0} -n \"CN={1}, O={2}\" -sky {3} -cy {4} -m 120 -a sha256 -eku 1.3.6.1.5.5.7.3.1 {5}";
private bool clearCertificates { get; set; }
/// <summary> /// <summary>
/// Cache dictionary /// Cache dictionary
/// </summary> /// </summary>
private readonly IDictionary<string, CachedCertificate> certificateCache; private readonly IDictionary<string, CachedCertificate> certificateCache;
/// <summary> private Action<Exception> exceptionFunc;
/// A lock to manage concurrency
/// </summary>
private SemaphoreSlim semaphoreLock = new SemaphoreSlim(1);
internal string Issuer { get; private set; } internal string Issuer { get; private set; }
internal string RootCertificateName { get; private set; } internal string RootCertificateName { get; private set; }
internal X509Store MyStore { get; private set; } internal X509Certificate2 rootCertificate { get; set; }
internal X509Store RootStore { get; private set; }
internal CertificateManager(string issuer, string rootCertificateName) internal CertificateManager(string issuer, string rootCertificateName, Action<Exception> exceptionFunc)
{ {
Issuer = issuer; this.exceptionFunc = exceptionFunc;
RootCertificateName = rootCertificateName;
MyStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); certEngine = new CertificateMaker();
RootStore = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
certificateCache = new Dictionary<string, CachedCertificate>(); Issuer = issuer;
RootCertificateName = rootCertificateName;
certificateCache = new ConcurrentDictionary<string, CachedCertificate>();
} }
/// <summary> internal X509Certificate2 GetRootCertificate()
/// Attempts to move a self-signed certificate to the root store.
/// </summary>
/// <returns>true if succeeded, else false</returns>
internal async Task<bool> CreateTrustedRootCertificate()
{ {
X509Certificate2 rootCertificate = var fileName = Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "rootCert.pfx");
await CreateCertificate(RootStore, RootCertificateName, true);
return rootCertificate != null; if (File.Exists(fileName))
{
try
{
return new X509Certificate2(fileName, string.Empty, X509KeyStorageFlags.Exportable);
}
catch (Exception e)
{
exceptionFunc(e);
return null;
}
}
return null;
} }
/// <summary> /// <summary>
/// Attempts to remove the self-signed certificate from the root store. /// Attempts to create a RootCertificate
/// </summary> /// </summary>
/// <returns>true if succeeded, else false</returns> /// <returns>true if succeeded, else false</returns>
internal bool DestroyTrustedRootCertificate() internal bool CreateTrustedRootCertificate()
{
return DestroyCertificate(RootStore, RootCertificateName, false);
}
internal X509Certificate2Collection FindCertificates(string certificateSubject)
{ {
return FindCertificates(MyStore, certificateSubject);
}
protected virtual X509Certificate2Collection FindCertificates(X509Store store, string certificateSubject)
{
X509Certificate2Collection discoveredCertificates = store.Certificates
.Find(X509FindType.FindBySubjectDistinguishedName, certificateSubject, false);
return discoveredCertificates.Count > 0 ?
discoveredCertificates : null;
}
internal async Task<X509Certificate2> CreateCertificate(string certificateName, bool isRootCertificate) rootCertificate = GetRootCertificate();
{ if (rootCertificate != null)
return await CreateCertificate(MyStore, certificateName, isRootCertificate); {
return true;
}
try
{
rootCertificate = CreateCertificate(RootCertificateName, true);
}
catch(Exception e)
{
exceptionFunc(e);
}
if (rootCertificate != null)
{
try
{
var fileName = Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "rootCert.pfx");
File.WriteAllBytes(fileName, rootCertificate.Export(X509ContentType.Pkcs12));
}
catch(Exception e)
{
exceptionFunc(e);
}
}
return rootCertificate != null;
} }
/// <summary> /// <summary>
/// Create an SSL certificate /// Create an SSL certificate
/// </summary> /// </summary>
...@@ -90,10 +100,8 @@ namespace Titanium.Web.Proxy.Network ...@@ -90,10 +100,8 @@ namespace Titanium.Web.Proxy.Network
/// <param name="certificateName"></param> /// <param name="certificateName"></param>
/// <param name="isRootCertificate"></param> /// <param name="isRootCertificate"></param>
/// <returns></returns> /// <returns></returns>
protected async virtual Task<X509Certificate2> CreateCertificate(X509Store store, string certificateName, bool isRootCertificate) internal virtual X509Certificate2 CreateCertificate(string certificateName, bool isRootCertificate)
{ {
await semaphoreLock.WaitAsync();
try try
{ {
if (certificateCache.ContainsKey(certificateName)) if (certificateCache.ContainsKey(certificateName))
...@@ -102,167 +110,46 @@ namespace Titanium.Web.Proxy.Network ...@@ -102,167 +110,46 @@ namespace Titanium.Web.Proxy.Network
cached.LastAccess = DateTime.Now; cached.LastAccess = DateTime.Now;
return cached.Certificate; return cached.Certificate;
} }
}
catch
{
X509Certificate2 certificate = null; }
store.Open(OpenFlags.ReadWrite); X509Certificate2 certificate = null;
string certificateSubject = string.Format("CN={0}, O={1}", certificateName, Issuer); lock (string.Intern(certificateName))
{
X509Certificate2Collection certificates; if (certificateCache.ContainsKey(certificateName) == false)
if (isRootCertificate)
{ {
certificates = FindCertificates(store, certificateSubject); try
if (certificates != null)
{ {
certificate = certificates[0]; certificate = certEngine.MakeCertificate(certificateName, isRootCertificate, rootCertificate);
} }
} catch(Exception e)
if (certificate == null)
{
string[] args = new[] {
GetCertificateCreateArgs(store, certificateName) };
await CreateCertificate(args);
certificates = FindCertificates(store, certificateSubject);
//remove it from store
if (!isRootCertificate)
{ {
DestroyCertificate(certificateName); exceptionFunc(e);
} }
if (certificate != null && !certificateCache.ContainsKey(certificateName))
if (certificates != null)
{ {
certificate = certificates[0]; certificateCache.Add(certificateName, new CachedCertificate() { Certificate = certificate });
} }
} }
else
store.Close();
if (certificate != null && !certificateCache.ContainsKey(certificateName))
{ {
certificateCache.Add(certificateName, new CachedCertificate() { Certificate = certificate }); if (certificateCache.ContainsKey(certificateName))
{
var cached = certificateCache[certificateName];
cached.LastAccess = DateTime.Now;
return cached.Certificate;
}
} }
return certificate;
} }
finally
{
semaphoreLock.Release();
}
}
/// <summary>
/// Create certificate using makecert.exe
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
protected virtual Task<int> CreateCertificate(string[] args)
{
// there is no non-generic TaskCompletionSource
var tcs = new TaskCompletionSource<int>();
var process = new Process();
string file = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "makecert.exe");
if (!File.Exists(file))
{
throw new Exception("Unable to locate 'makecert.exe'.");
}
process.StartInfo.Verb = "runas";
process.StartInfo.Arguments = args != null ? args[0] : string.Empty;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = file;
process.EnableRaisingEvents = true;
process.Exited += (sender, processArgs) =>
{
tcs.SetResult(process.ExitCode);
process.Dispose();
};
bool started = process.Start();
if (!started)
{
//you may allow for the process to be re-used (started = false)
//but I'm not sure about the guarantees of the Exited event in such a case
throw new InvalidOperationException("Could not start process: " + process);
}
return tcs.Task;
}
/// <summary>
/// Destroy an SSL certificate from local store
/// </summary>
/// <param name="certificateName"></param>
/// <returns></returns>
internal bool DestroyCertificate(string certificateName)
{
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)
{
X509Certificate2Collection certificates = null;
store.Open(OpenFlags.ReadWrite);
string certificateSubject = string.Format("CN={0}, O={1}", certificateName, Issuer);
certificates = FindCertificates(store, certificateSubject); return certificate;
if (certificates != null)
{
store.RemoveRange(certificates);
certificates = FindCertificates(store, certificateSubject);
}
store.Close();
if (removeFromCache &&
certificateCache.ContainsKey(certificateName))
{
certificateCache.Remove(certificateName);
}
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)
{
bool isRootCertificate =
(certificateName == RootCertificateName);
string certCreatArgs = string.Format(CertCreateFormat,
store.Name, certificateName, Issuer,
isRootCertificate ? "signature" : "exchange",
isRootCertificate ? "authority" : "end",
isRootCertificate ? "-h 1 -r" : string.Format("-pe -in \"{0}\" -is Root", RootCertificateName));
return certCreatArgs;
} }
private bool clearCertificates { get; set; }
/// <summary> /// <summary>
/// Stops the certificate cache clear process /// Stops the certificate cache clear process
/// </summary> /// </summary>
...@@ -279,7 +166,6 @@ namespace Titanium.Web.Proxy.Network ...@@ -279,7 +166,6 @@ namespace Titanium.Web.Proxy.Network
clearCertificates = true; clearCertificates = true;
while (clearCertificates) while (clearCertificates)
{ {
await semaphoreLock.WaitAsync();
try try
{ {
...@@ -292,26 +178,49 @@ namespace Titanium.Web.Proxy.Network ...@@ -292,26 +178,49 @@ namespace Titanium.Web.Proxy.Network
foreach (var cache in outdated) foreach (var cache in outdated)
certificateCache.Remove(cache.Key); certificateCache.Remove(cache.Key);
} }
finally { finally
semaphoreLock.Release(); {
} }
//after a minute come back to check for outdated certificates in cache //after a minute come back to check for outdated certificates in cache
await Task.Delay(1000 * 60); await Task.Delay(1000 * 60);
} }
} }
public void Dispose() internal bool TrustRootCertificate()
{ {
if (MyStore != null) if (rootCertificate == null)
{ {
MyStore.Close(); return false;
} }
try
{
X509Store x509RootStore = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
var x509PersonalStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
x509RootStore.Open(OpenFlags.ReadWrite);
x509PersonalStore.Open(OpenFlags.ReadWrite);
if (RootStore != null) try
{
x509RootStore.Add(rootCertificate);
x509PersonalStore.Add(rootCertificate);
}
finally
{
x509RootStore.Close();
x509PersonalStore.Close();
}
return true;
}
catch
{ {
RootStore.Close(); return false;
} }
} }
public void Dispose()
{
}
} }
} }
\ No newline at end of file
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
using System.IO; using System.IO;
using System.Net.Sockets; using System.Net.Sockets;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network namespace Titanium.Web.Proxy.Network
{ {
...@@ -10,11 +11,20 @@ namespace Titanium.Web.Proxy.Network ...@@ -10,11 +11,20 @@ namespace Titanium.Web.Proxy.Network
/// </summary> /// </summary>
public class TcpConnection : IDisposable public class TcpConnection : IDisposable
{ {
internal ExternalProxy UpStreamHttpProxy { get; set; }
internal ExternalProxy UpStreamHttpsProxy { get; set; }
internal string HostName { get; set; } internal string HostName { get; set; }
internal int port { get; set; } internal int Port { get; set; }
internal bool IsHttps { get; set; } internal bool IsHttps { get; set; }
/// <summary>
/// Http version
/// </summary>
internal Version Version { get; set; }
internal TcpClient TcpClient { get; set; } internal TcpClient TcpClient { get; set; }
/// <summary> /// <summary>
...@@ -27,6 +37,16 @@ namespace Titanium.Web.Proxy.Network ...@@ -27,6 +37,16 @@ namespace Titanium.Web.Proxy.Network
/// </summary> /// </summary>
internal Stream Stream { get; set; } internal Stream Stream { get; set; }
/// <summary>
/// Last time this connection was used
/// </summary>
internal DateTime LastAccess { get; set; }
internal TcpConnection()
{
LastAccess = DateTime.Now;
}
public void Dispose() public void Dispose()
{ {
Stream.Close(); Stream.Close();
......
...@@ -7,6 +7,7 @@ using System.Net.Security; ...@@ -7,6 +7,7 @@ using System.Net.Security;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using System.Security.Authentication; using System.Security.Authentication;
using System.Linq;
namespace Titanium.Web.Proxy.Network namespace Titanium.Web.Proxy.Network
{ {
...@@ -34,7 +35,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -34,7 +35,7 @@ namespace Titanium.Web.Proxy.Network
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <returns></returns> /// <returns></returns>
internal async Task<TcpConnection> CreateClient(int bufferSize, int connectionTimeOutSeconds, internal async Task<TcpConnection> CreateClient(int bufferSize, int connectionTimeOutSeconds,
string remoteHostName, int remotePort, Version httpVersion, string remoteHostName, int remotePort, Version httpVersion,
bool isHttps, SslProtocols supportedSslProtocols, bool isHttps, SslProtocols supportedSslProtocols,
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback, RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy, ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy,
...@@ -48,16 +49,22 @@ namespace Titanium.Web.Proxy.Network ...@@ -48,16 +49,22 @@ 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 this proxy uses another external proxy then create a tunnel request for HTTPS connections
if (externalHttpsProxy != null) if (externalHttpsProxy != null && externalHttpsProxy.HostName != remoteHostName)
{ {
client = new TcpClient(externalHttpsProxy.HostName, externalHttpsProxy.Port); client = new TcpClient(externalHttpsProxy.HostName, externalHttpsProxy.Port);
stream = client.GetStream(); stream = client.GetStream();
using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true)) using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true))
{ {
await writer.WriteLineAsync(string.Format("CONNECT {0}:{1} {2}", remoteHostName, remotePort, httpVersion)); await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
await writer.WriteLineAsync(string.Format("Host: {0}:{1}", remoteHostName, remotePort)); await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
await writer.WriteLineAsync("Connection: Keep-Alive"); await writer.WriteLineAsync("Connection: Keep-Alive");
if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null)
{
await writer.WriteLineAsync("Proxy-Connection: keep-alive");
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password)));
}
await writer.WriteLineAsync(); await writer.WriteLineAsync();
await writer.FlushAsync(); await writer.FlushAsync();
writer.Close(); writer.Close();
...@@ -67,7 +74,8 @@ namespace Titanium.Web.Proxy.Network ...@@ -67,7 +74,8 @@ namespace Titanium.Web.Proxy.Network
{ {
var result = await reader.ReadLineAsync(); var result = await reader.ReadLineAsync();
if (!result.ToLower().Contains("200 connection established"))
if (!new string[] { "200 OK", "connection established" }.Any(s=> result.ToLower().Contains(s.ToLower())))
{ {
throw new Exception("Upstream proxy failed to create a secure tunnel"); throw new Exception("Upstream proxy failed to create a secure tunnel");
} }
...@@ -92,17 +100,14 @@ namespace Titanium.Web.Proxy.Network ...@@ -92,17 +100,14 @@ namespace Titanium.Web.Proxy.Network
} }
catch catch
{ {
if (sslStream != null) sslStream?.Dispose();
{
sslStream.Dispose();
}
throw; throw;
} }
} }
else else
{ {
if (externalHttpProxy != null) if (externalHttpProxy != null && externalHttpProxy.HostName != remoteHostName)
{ {
client = new TcpClient(externalHttpProxy.HostName, externalHttpProxy.Port); client = new TcpClient(externalHttpProxy.HostName, externalHttpProxy.Port);
stream = client.GetStream(); stream = client.GetStream();
...@@ -119,15 +124,18 @@ namespace Titanium.Web.Proxy.Network ...@@ -119,15 +124,18 @@ namespace Titanium.Web.Proxy.Network
stream.ReadTimeout = connectionTimeOutSeconds * 1000; stream.ReadTimeout = connectionTimeOutSeconds * 1000;
stream.WriteTimeout = connectionTimeOutSeconds * 1000; stream.WriteTimeout = connectionTimeOutSeconds * 1000;
return new TcpConnection() return new TcpConnection()
{ {
UpStreamHttpProxy = externalHttpProxy,
UpStreamHttpsProxy = externalHttpsProxy,
HostName = remoteHostName, HostName = remoteHostName,
port = remotePort, Port = remotePort,
IsHttps = isHttps, IsHttps = isHttps,
TcpClient = client, TcpClient = client,
StreamReader = new CustomBinaryReader(stream), StreamReader = new CustomBinaryReader(stream),
Stream = stream Stream = stream,
Version = httpVersion
}; };
} }
} }
......
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy
{
public partial class ProxyServer
{
private async Task<bool> CheckAuthorization(StreamWriter clientStreamWriter, IEnumerable<HttpHeader> Headers)
{
if (AuthenticateUserFunc == null)
{
return true;
}
var httpHeaders = Headers as HttpHeader[] ?? Headers.ToArray();
try
{
if (!httpHeaders.Where(t => t.Name == "Proxy-Authorization").Any())
{
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Required", clientStreamWriter);
var response = new Response();
response.ResponseHeaders = new Dictionary<string, HttpHeader>();
response.ResponseHeaders.Add("Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\""));
response.ResponseHeaders.Add("Proxy-Connection", new HttpHeader("Proxy-Connection", "close"));
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
return false;
}
else
{
var headerValue = httpHeaders.Where(t => t.Name == "Proxy-Authorization").FirstOrDefault().Value.Trim();
if (!headerValue.ToLower().StartsWith("basic"))
{
//Return not authorized
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Invalid", clientStreamWriter);
var response = new Response();
response.ResponseHeaders = new Dictionary<string, HttpHeader>();
response.ResponseHeaders.Add("Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\""));
response.ResponseHeaders.Add("Proxy-Connection", new HttpHeader("Proxy-Connection", "close"));
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
return false;
}
headerValue = headerValue.Substring(5).Trim();
var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(headerValue));
if (decoded.Contains(":") == false)
{
//Return not authorized
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Invalid", clientStreamWriter);
var response = new Response();
response.ResponseHeaders = new Dictionary<string, HttpHeader>();
response.ResponseHeaders.Add("Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\""));
response.ResponseHeaders.Add("Proxy-Connection", new HttpHeader("Proxy-Connection", "close"));
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
return false;
}
var username = decoded.Substring(0, decoded.IndexOf(':'));
var password = decoded.Substring(decoded.IndexOf(':') + 1);
return await AuthenticateUserFunc(username, password).ConfigureAwait(false);
}
}
catch (Exception e)
{
ExceptionFunc(new ProxyAuthorizationException("Error whilst authorizing request", e, httpHeaders));
//Return not authorized
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Invalid", clientStreamWriter);
var response = new Response();
response.ResponseHeaders = new Dictionary<string, HttpHeader>();
response.ResponseHeaders.Add("Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\""));
response.ResponseHeaders.Add("Proxy-Connection", new HttpHeader("Proxy-Connection", "close"));
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
return false;
}
}
}
}
...@@ -17,10 +17,11 @@ namespace Titanium.Web.Proxy ...@@ -17,10 +17,11 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public partial class ProxyServer : IDisposable public partial class ProxyServer : IDisposable
{ {
/// <summary> /// <summary>
/// Does the root certificate used by this proxy is trusted by the machine? /// Is the root certificate used by this proxy is valid?
/// </summary> /// </summary>
private bool certTrusted { get; set; } private bool certValidated { get; set; }
/// <summary> /// <summary>
/// Is the proxy currently running /// Is the proxy currently running
...@@ -32,6 +33,16 @@ namespace Titanium.Web.Proxy ...@@ -32,6 +33,16 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
private CertificateManager certificateCacheManager { get; set; } private CertificateManager certificateCacheManager { get; set; }
/// <summary>
/// An default exception log func
/// </summary>
private readonly Lazy<Action<Exception>> defaultExceptionFunc = new Lazy<Action<Exception>>(() => (e => { }));
/// <summary>
/// backing exception func for exposed public property
/// </summary>
private Action<Exception> exceptionFunc;
/// <summary> /// <summary>
/// A object that creates tcp connection to server /// A object that creates tcp connection to server
/// </summary> /// </summary>
...@@ -56,9 +67,19 @@ namespace Titanium.Web.Proxy ...@@ -56,9 +67,19 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// Name of the root certificate /// Name of the root certificate
/// If no certificate is provided then a default Root Certificate will be created and used
/// The provided root certificate has to be in the proxy exe directory with the private key
/// The root certificate file should be named as "rootCert.pfx"
/// </summary> /// </summary>
public string RootCertificateName { get; set; } public string RootCertificateName { get; set; }
/// <summary>
/// Trust the RootCertificate used by this proxy server
/// Note that this do not make the client trust the certificate!
/// This would import the root certificate to the certificate store of machine that runs this proxy server
/// </summary>
public bool TrustRootCertificate { get; set; }
/// <summary> /// <summary>
/// Does this proxy uses the HTTP protocol 100 continue behaviour strictly? /// Does this proxy uses the HTTP protocol 100 continue behaviour strictly?
/// Broken 100 contunue implementations on server/client may cause problems if enabled /// Broken 100 contunue implementations on server/client may cause problems if enabled
...@@ -88,12 +109,12 @@ namespace Titanium.Web.Proxy ...@@ -88,12 +109,12 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// External proxy for Http /// External proxy for Http
/// </summary> /// </summary>
public ExternalProxy ExternalHttpProxy { get; set; } public ExternalProxy UpStreamHttpProxy { get; set; }
/// <summary> /// <summary>
/// External proxy for Https /// External proxy for Http
/// </summary> /// </summary>
public ExternalProxy ExternalHttpsProxy { get; set; } public ExternalProxy UpStreamHttpsProxy { get; set; }
/// <summary> /// <summary>
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication /// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication
...@@ -105,6 +126,51 @@ namespace Titanium.Web.Proxy ...@@ -105,6 +126,51 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public event Func<object, CertificateSelectionEventArgs, Task> ClientCertificateSelectionCallback; public event Func<object, CertificateSelectionEventArgs, Task> ClientCertificateSelectionCallback;
/// <summary>
/// Callback for error events in proxy
/// </summary>
public Action<Exception> ExceptionFunc
{
get
{
return exceptionFunc ?? defaultExceptionFunc.Value;
}
set
{
exceptionFunc = value;
}
}
/// <summary>
/// A callback to authenticate clients
/// Parameters are username, password provided by client
/// return true for successful authentication
/// </summary>
public Func<string, string, Task<bool>> AuthenticateUserFunc
{
get;
set;
}
/// <summary>
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP requests
/// return the ExternalProxy object with valid credentials
/// </summary>
public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpProxyFunc
{
get;
set;
}
/// <summary>
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTPS requests
/// return the ExternalProxy object with valid credentials
public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpsProxyFunc
{
get;
set;
}
/// <summary> /// <summary>
/// A list of IpAddress & port this proxy is listening to /// A list of IpAddress & port this proxy is listening to
/// </summary> /// </summary>
...@@ -115,11 +181,25 @@ namespace Titanium.Web.Proxy ...@@ -115,11 +181,25 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3; public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
/// <summary>
/// Is the proxy currently running
/// </summary>
public bool ProxyRunning => proxyRunning;
/// <summary>
/// Gets or sets a value indicating whether requests will be chained to upstream gateway.
/// </summary>
public bool ForwardToUpstreamGateway { get; set; }
/// <summary> /// <summary>
/// Constructor /// Constructor
/// </summary> /// </summary>
public ProxyServer() public ProxyServer() : this(null, null) { }
public ProxyServer(string rootCertificateName, string rootCertificateIssuerName)
{ {
RootCertificateName = rootCertificateName;
RootCertificateIssuerName = rootCertificateIssuerName;
//default values //default values
ConnectionTimeOutSeconds = 120; ConnectionTimeOutSeconds = 120;
CertificateCacheTimeOutMinutes = 60; CertificateCacheTimeOutMinutes = 60;
...@@ -131,9 +211,6 @@ namespace Titanium.Web.Proxy ...@@ -131,9 +211,6 @@ namespace Titanium.Web.Proxy
RootCertificateName = RootCertificateName ?? "Titanium Root Certificate Authority"; RootCertificateName = RootCertificateName ?? "Titanium Root Certificate Authority";
RootCertificateIssuerName = RootCertificateIssuerName ?? "Titanium"; RootCertificateIssuerName = RootCertificateIssuerName ?? "Titanium";
certificateCacheManager = new CertificateManager(RootCertificateIssuerName,
RootCertificateName);
} }
/// <summary> /// <summary>
...@@ -142,7 +219,7 @@ namespace Titanium.Web.Proxy ...@@ -142,7 +219,7 @@ namespace Titanium.Web.Proxy
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
public void AddEndPoint(ProxyEndPoint endPoint) public void AddEndPoint(ProxyEndPoint endPoint)
{ {
if (ProxyEndPoints.Any(x => x.IpAddress == endPoint.IpAddress && x.Port == endPoint.Port)) if (ProxyEndPoints.Any(x => x.IpAddress.Equals(endPoint.IpAddress) && endPoint.Port != 0 && x.Port == endPoint.Port))
{ {
throw new Exception("Cannot add another endpoint to same port & ip address"); throw new Exception("Cannot add another endpoint to same port & ip address");
} }
...@@ -216,13 +293,14 @@ namespace Titanium.Web.Proxy ...@@ -216,13 +293,14 @@ namespace Titanium.Web.Proxy
//If certificate was trusted by the machine //If certificate was trusted by the machine
if (certTrusted) if (certValidated)
{ {
systemProxySettingsManager.SetHttpsProxy( systemProxySettingsManager.SetHttpsProxy(
Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Loopback) ? "127.0.0.1" : endPoint.IpAddress.ToString(), Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Loopback) ? "127.0.0.1" : endPoint.IpAddress.ToString(),
endPoint.Port); endPoint.Port);
} }
endPoint.IsSystemHttpsProxy = true; endPoint.IsSystemHttpsProxy = true;
#if !DEBUG #if !DEBUG
...@@ -265,7 +343,21 @@ namespace Titanium.Web.Proxy ...@@ -265,7 +343,21 @@ namespace Titanium.Web.Proxy
throw new Exception("Proxy is already running."); throw new Exception("Proxy is already running.");
} }
certTrusted = certificateCacheManager.CreateTrustedRootCertificate().Result; certificateCacheManager = new CertificateManager(RootCertificateIssuerName,
RootCertificateName, ExceptionFunc);
certValidated = certificateCacheManager.CreateTrustedRootCertificate();
if (TrustRootCertificate)
{
certificateCacheManager.TrustRootCertificate();
}
if (ForwardToUpstreamGateway && GetCustomUpStreamHttpProxyFunc == null && GetCustomUpStreamHttpsProxyFunc == null)
{
GetCustomUpStreamHttpProxyFunc = GetSystemUpStreamProxy;
GetCustomUpStreamHttpsProxyFunc = GetSystemUpStreamProxy;
}
foreach (var endPoint in ProxyEndPoints) foreach (var endPoint in ProxyEndPoints)
{ {
...@@ -277,6 +369,28 @@ namespace Titanium.Web.Proxy ...@@ -277,6 +369,28 @@ namespace Titanium.Web.Proxy
proxyRunning = true; proxyRunning = true;
} }
/// <summary>
/// Gets the system up stream proxy.
/// </summary>
/// <param name="sessionEventArgs">The <see cref="SessionEventArgs"/> instance containing the event data.</param>
/// <returns><see cref="ExternalProxy"/> instance containing valid proxy configuration from PAC/WAPD scripts if any exists.</returns>
private Task<ExternalProxy> GetSystemUpStreamProxy(SessionEventArgs sessionEventArgs)
{
// Use built-in WebProxy class to handle PAC/WAPD scripts.
var systemProxyResolver = new WebProxy();
var systemProxyUri = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri);
// TODO: Apply authorization
var systemProxy = new ExternalProxy
{
HostName = systemProxyUri.Host,
Port = systemProxyUri.Port
};
return Task.FromResult(systemProxy);
}
/// <summary> /// <summary>
/// Stop this proxy server /// Stop this proxy server
/// </summary> /// </summary>
...@@ -304,7 +418,7 @@ namespace Titanium.Web.Proxy ...@@ -304,7 +418,7 @@ namespace Titanium.Web.Proxy
ProxyEndPoints.Clear(); ProxyEndPoints.Clear();
certificateCacheManager.StopClearIdleCertificates(); certificateCacheManager?.StopClearIdleCertificates();
proxyRunning = false; proxyRunning = false;
} }
...@@ -378,32 +492,40 @@ namespace Titanium.Web.Proxy ...@@ -378,32 +492,40 @@ namespace Titanium.Web.Proxy
//Other errors are discarded to keep proxy running //Other errors are discarded to keep proxy running
} }
if (tcpClient != null) if (tcpClient != null)
{ {
Task.Run(async () => Task.Run(async () =>
{ {
try try
{ {
if (endPoint.GetType() == typeof(TransparentProxyEndPoint)) if (endPoint.GetType() == typeof(TransparentProxyEndPoint))
{ {
await HandleClient(endPoint as TransparentProxyEndPoint, tcpClient); await HandleClient(endPoint as TransparentProxyEndPoint, tcpClient);
} }
else else
{ {
await HandleClient(endPoint as ExplicitProxyEndPoint, tcpClient); await HandleClient(endPoint as ExplicitProxyEndPoint, tcpClient);
} }
} }
finally finally
{ {
if (tcpClient != null) if (tcpClient != null)
{ {
//This line is important!
//contributors please don't remove it without discussion
//It helps to avoid eventual deterioration of performance due to TCP port exhaustion
//due to default TCP CLOSE_WAIT timeout for 4 minutes
tcpClient.LingerState = new LingerOption(true, 0); tcpClient.LingerState = new LingerOption(true, 0);
tcpClient.Client.Shutdown(SocketShutdown.Both); tcpClient.Client.Shutdown(SocketShutdown.Both);
tcpClient.Client.Close(); tcpClient.Client.Close();
tcpClient.Client.Dispose(); tcpClient.Client.Dispose();
tcpClient.Close(); tcpClient.Close();
} }
} }
}); });
...@@ -420,7 +542,7 @@ namespace Titanium.Web.Proxy ...@@ -420,7 +542,7 @@ namespace Titanium.Web.Proxy
Stop(); Stop();
} }
certificateCacheManager.Dispose(); certificateCacheManager?.Dispose();
} }
} }
} }
\ No newline at end of file
...@@ -2,10 +2,12 @@ ...@@ -2,10 +2,12 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Security; using System.Net.Security;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Authentication; using System.Security.Authentication;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Network;
...@@ -23,11 +25,14 @@ namespace Titanium.Web.Proxy ...@@ -23,11 +25,14 @@ namespace Titanium.Web.Proxy
/// </summary> /// </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
//So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy //So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient client) private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient)
{ {
Stream clientStream = client.GetStream();
Stream clientStream = tcpClient.GetStream();
clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000; clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000;
clientStream.WriteTimeout = ConnectionTimeOutSeconds * 1000; clientStream.WriteTimeout = ConnectionTimeOutSeconds * 1000;
...@@ -77,11 +82,28 @@ namespace Titanium.Web.Proxy ...@@ -77,11 +82,28 @@ namespace Titanium.Web.Proxy
var excluded = endPoint.ExcludedHttpsHostNameRegex != null ? var excluded = endPoint.ExcludedHttpsHostNameRegex != null ?
endPoint.ExcludedHttpsHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x)) : false; endPoint.ExcludedHttpsHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x)) : false;
List<HttpHeader> connectRequestHeaders = null;
//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(); string tmpLine = null;
connectRequestHeaders = new List<HttpHeader>();
while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync()))
{
var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);
var newHeader = new HttpHeader(header[0], header[1]);
connectRequestHeaders.Add(newHeader);
}
if (await CheckAuthorization(clientStreamWriter, connectRequestHeaders) == false)
{
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
return;
}
await WriteConnectResponse(clientStreamWriter, version); await WriteConnectResponse(clientStreamWriter, version);
...@@ -91,7 +113,7 @@ namespace Titanium.Web.Proxy ...@@ -91,7 +113,7 @@ namespace Titanium.Web.Proxy
{ {
sslStream = new SslStream(clientStream, true); sslStream = new SslStream(clientStream, true);
var certificate = await certificateCacheManager.CreateCertificate(httpRemoteUri.Host, false); var certificate = certificateCacheManager.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,
SupportedSslProtocols, false); SupportedSslProtocols, false);
...@@ -125,7 +147,6 @@ namespace Titanium.Web.Proxy ...@@ -125,7 +147,6 @@ namespace Titanium.Web.Proxy
//write back successfull CONNECT response //write back successfull CONNECT response
await WriteConnectResponse(clientStreamWriter, version); await WriteConnectResponse(clientStreamWriter, version);
await TcpHelper.SendRaw(BUFFER_SIZE, ConnectionTimeOutSeconds, httpRemoteUri.Host, httpRemoteUri.Port, await TcpHelper.SendRaw(BUFFER_SIZE, ConnectionTimeOutSeconds, httpRemoteUri.Host, httpRemoteUri.Port,
httpCmd, version, null, httpCmd, version, null,
false, SupportedSslProtocols, false, SupportedSslProtocols,
...@@ -136,12 +157,11 @@ namespace Titanium.Web.Proxy ...@@ -136,12 +157,11 @@ namespace Titanium.Web.Proxy
Dispose(clientStream, clientStreamReader, clientStreamWriter, null); Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
return; return;
} }
//Now create the request //Now create the request
await HandleHttpSessionRequest(client, httpCmd, clientStream, clientStreamReader, clientStreamWriter, await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
httpRemoteUri.Scheme == Uri.UriSchemeHttps ? httpRemoteUri.Host : null); httpRemoteUri.Scheme == Uri.UriSchemeHttps ? httpRemoteUri.Host : null, endPoint, connectRequestHeaders, null, null);
} }
catch catch (Exception)
{ {
Dispose(clientStream, clientStreamReader, clientStreamWriter, null); Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
} }
...@@ -151,6 +171,7 @@ namespace Titanium.Web.Proxy ...@@ -151,6 +171,7 @@ namespace Titanium.Web.Proxy
//So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client //So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient) private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient)
{ {
Stream clientStream = tcpClient.GetStream(); Stream clientStream = tcpClient.GetStream();
clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000; clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000;
...@@ -165,7 +186,7 @@ namespace Titanium.Web.Proxy ...@@ -165,7 +186,7 @@ namespace Titanium.Web.Proxy
var sslStream = new SslStream(clientStream, true); var sslStream = new SslStream(clientStream, true);
//implement in future once SNI supported by SSL stream, for now use the same certificate //implement in future once SNI supported by SSL stream, for now use the same certificate
certificate = await certificateCacheManager.CreateCertificate(endPoint.GenericCertificateName, false); certificate = certificateCacheManager.CreateCertificate(endPoint.GenericCertificateName, false);
try try
{ {
...@@ -180,10 +201,7 @@ namespace Titanium.Web.Proxy ...@@ -180,10 +201,7 @@ namespace Titanium.Web.Proxy
} }
catch (Exception) catch (Exception)
{ {
if (sslStream != null) sslStream.Dispose();
{
sslStream.Dispose();
}
Dispose(sslStream, clientStreamReader, clientStreamWriter, null); Dispose(sslStream, clientStreamReader, clientStreamWriter, null);
return; return;
...@@ -193,6 +211,7 @@ namespace Titanium.Web.Proxy ...@@ -193,6 +211,7 @@ namespace Titanium.Web.Proxy
else else
{ {
clientStreamReader = new CustomBinaryReader(clientStream); clientStreamReader = new CustomBinaryReader(clientStream);
clientStreamWriter = new StreamWriter(clientStream);
} }
//now read the request line //now read the request line
...@@ -200,8 +219,134 @@ namespace Titanium.Web.Proxy ...@@ -200,8 +219,134 @@ namespace Titanium.Web.Proxy
//Now create the request //Now create the request
await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter, await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
endPoint.EnableSsl ? endPoint.GenericCertificateName : null); endPoint.EnableSsl ? endPoint.GenericCertificateName : null, endPoint, null);
}
private async Task HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args, ExternalProxy customUpStreamHttpProxy, ExternalProxy customUpStreamHttpsProxy, bool CloseConnection)
{
try
{
if (connection == null)
{
if (args.WebSession.Request.RequestUri.Scheme == "http")
{
if (GetCustomUpStreamHttpProxyFunc != null)
{
customUpStreamHttpProxy = await GetCustomUpStreamHttpProxyFunc(args).ConfigureAwait(false);
}
}
else
{
if (GetCustomUpStreamHttpsProxyFunc != null)
{
customUpStreamHttpsProxy = await GetCustomUpStreamHttpsProxyFunc(args).ConfigureAwait(false);
}
}
args.CustomUpStreamHttpProxyUsed = customUpStreamHttpProxy;
args.CustomUpStreamHttpsProxyUsed = customUpStreamHttpsProxy;
connection = await tcpConnectionFactory.CreateClient(BUFFER_SIZE, ConnectionTimeOutSeconds,
args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.WebSession.Request.HttpVersion,
args.IsHttps, SupportedSslProtocols,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
new LocalCertificateSelectionCallback(SelectClientCertificate),
customUpStreamHttpProxy ?? UpStreamHttpProxy, customUpStreamHttpsProxy ?? UpStreamHttpsProxy, args.ProxyClient.ClientStream);
}
args.WebSession.Request.RequestLocked = true;
//If request was cancelled by user then dispose the client
if (args.WebSession.Request.CancelRequest)
{
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args);
return;
}
//if expect continue is enabled then send the headers first
//and see if server would return 100 conitinue
if (args.WebSession.Request.ExpectContinue)
{
args.WebSession.SetConnection(connection);
await args.WebSession.SendRequest(Enable100ContinueBehaviour);
}
//If 100 continue was the response inform that to the client
if (Enable100ContinueBehaviour)
{
if (args.WebSession.Request.Is100Continue)
{
await WriteResponseStatus(args.WebSession.Response.HttpVersion, "100",
"Continue", args.ProxyClient.ClientStreamWriter);
await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
}
else if (args.WebSession.Request.ExpectationFailed)
{
await WriteResponseStatus(args.WebSession.Response.HttpVersion, "417",
"Expectation Failed", args.ProxyClient.ClientStreamWriter);
await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
}
}
//If expect continue is not enabled then set the connectio and send request headers
if (!args.WebSession.Request.ExpectContinue)
{
args.WebSession.SetConnection(connection);
await args.WebSession.SendRequest(Enable100ContinueBehaviour);
}
//If request was modified by user
if (args.WebSession.Request.RequestBodyRead)
{
if (args.WebSession.Request.ContentEncoding != null)
{
args.WebSession.Request.RequestBody = await GetCompressedResponseBody(args.WebSession.Request.ContentEncoding, args.WebSession.Request.RequestBody);
}
//chunked send is not supported as of now
args.WebSession.Request.ContentLength = args.WebSession.Request.RequestBody.Length;
var newStream = args.WebSession.ServerConnection.Stream;
await newStream.WriteAsync(args.WebSession.Request.RequestBody, 0, args.WebSession.Request.RequestBody.Length);
}
else
{
if (!args.WebSession.Request.ExpectationFailed)
{
//If its a post/put request, then read the client html body and send it to server
if (args.WebSession.Request.Method.ToUpper() == "POST" || args.WebSession.Request.Method.ToUpper() == "PUT")
{
await SendClientRequestBody(args);
}
}
}
//If not expectation failed response was returned by server then parse response
if (!args.WebSession.Request.ExpectationFailed)
{
await HandleHttpSessionResponse(args);
}
//if connection is closing exit
if (args.WebSession.Response.ResponseKeepAlive == false)
{
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args);
return;
}
}
catch (Exception e)
{
ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request (internal)", e, args));
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args);
return;
}
if (CloseConnection)
{
//dispose
connection?.Dispose();
}
} }
/// <summary> /// <summary>
/// This is the core request handler method for a particular connection from client /// This is the core request handler method for a particular connection from client
/// </summary> /// </summary>
...@@ -213,7 +358,7 @@ namespace Titanium.Web.Proxy ...@@ -213,7 +358,7 @@ namespace Titanium.Web.Proxy
/// <param name="httpsHostName"></param> /// <param name="httpsHostName"></param>
/// <returns></returns> /// <returns></returns>
private async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream, private async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName) CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName, ProxyEndPoint endPoint, List<HttpHeader> connectHeaders, ExternalProxy customUpStreamHttpProxy = null, ExternalProxy customUpStreamHttpsProxy = null)
{ {
TcpConnection connection = null; TcpConnection connection = null;
...@@ -229,7 +374,22 @@ namespace Titanium.Web.Proxy ...@@ -229,7 +374,22 @@ namespace Titanium.Web.Proxy
var args = new SessionEventArgs(BUFFER_SIZE, HandleHttpSessionResponse); var args = new SessionEventArgs(BUFFER_SIZE, HandleHttpSessionResponse);
args.ProxyClient.TcpClient = client; args.ProxyClient.TcpClient = client;
args.WebSession.ConnectHeaders = connectHeaders;
args.WebSession.ProcessId = new Lazy<int>(() =>
{
var remoteEndPoint = (IPEndPoint)args.ProxyClient.TcpClient.Client.RemoteEndPoint;
//If client is localhost get the process id
if (NetworkHelper.IsLocalIpAddress(remoteEndPoint.Address))
{
return NetworkHelper.GetProcessIdFromPort(remoteEndPoint.Port, endPoint.IpV6Enabled);
}
//can't access process Id of remote request from remote machine
return -1;
});
try try
{ {
//break up the line into three components (method, remote URL & Http Version) //break up the line into three components (method, remote URL & Http Version)
...@@ -249,6 +409,7 @@ namespace Titanium.Web.Proxy ...@@ -249,6 +409,7 @@ namespace Titanium.Web.Proxy
} }
} }
//Read the request headers in to unique and non-unique header collections //Read the request headers in to unique and non-unique header collections
string tmpLine; string tmpLine;
while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync())) while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync()))
...@@ -294,6 +455,13 @@ namespace Titanium.Web.Proxy ...@@ -294,6 +455,13 @@ namespace Titanium.Web.Proxy
args.ProxyClient.ClientStreamReader = clientStreamReader; args.ProxyClient.ClientStreamReader = clientStreamReader;
args.ProxyClient.ClientStreamWriter = clientStreamWriter; args.ProxyClient.ClientStreamWriter = clientStreamWriter;
if (httpsHostName == null && (await CheckAuthorization(clientStreamWriter, args.WebSession.Request.RequestHeaders.Values) == false))
{
Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
break;
}
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;
...@@ -325,92 +493,17 @@ namespace Titanium.Web.Proxy ...@@ -325,92 +493,17 @@ namespace Titanium.Web.Proxy
} }
//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.
if (connection == null) await HandleHttpSessionRequestInternal(connection, args, customUpStreamHttpProxy, customUpStreamHttpsProxy, false).ConfigureAwait(false);
{
connection = await tcpConnectionFactory.CreateClient(BUFFER_SIZE, ConnectionTimeOutSeconds,
args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, httpVersion,
args.IsHttps, SupportedSslProtocols,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
new LocalCertificateSelectionCallback(SelectClientCertificate),
ExternalHttpProxy, ExternalHttpsProxy, clientStream);
}
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(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)
{
args.WebSession.SetConnection(connection);
await args.WebSession.SendRequest(Enable100ContinueBehaviour);
}
//If 100 continue was the response inform that to the client
if (Enable100ContinueBehaviour)
{
if (args.WebSession.Request.Is100Continue)
{
await WriteResponseStatus(args.WebSession.Response.HttpVersion, "100",
"Continue", args.ProxyClient.ClientStreamWriter);
await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
}
else if (args.WebSession.Request.ExpectationFailed)
{
await WriteResponseStatus(args.WebSession.Response.HttpVersion, "417",
"Expectation Failed", args.ProxyClient.ClientStreamWriter);
await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
}
}
//If expect continue is not enabled then set the connectio and send request headers
if (!args.WebSession.Request.ExpectContinue)
{
args.WebSession.SetConnection(connection);
await args.WebSession.SendRequest(Enable100ContinueBehaviour);
}
//If request was modified by user
if (args.WebSession.Request.RequestBodyRead)
{
if (args.WebSession.Request.ContentEncoding != null)
{
args.WebSession.Request.RequestBody = await GetCompressedResponseBody(args.WebSession.Request.ContentEncoding, args.WebSession.Request.RequestBody);
}
//chunked send is not supported as of now
args.WebSession.Request.ContentLength = args.WebSession.Request.RequestBody.Length;
var newStream = args.WebSession.ServerConnection.Stream;
await newStream.WriteAsync(args.WebSession.Request.RequestBody, 0, args.WebSession.Request.RequestBody.Length);
}
else
{
if (!args.WebSession.Request.ExpectationFailed)
{
//If its a post/put request, then read the client html body and send it to server
if (httpMethod.ToUpper() == "POST" || httpMethod.ToUpper() == "PUT")
{
await SendClientRequestBody(args);
}
}
}
//If not expectation failed response was returned by server then parse response
if (!args.WebSession.Request.ExpectationFailed)
{
await HandleHttpSessionResponse(args);
}
//if connection is closing exit //if connection is closing exit
if (args.WebSession.Response.ResponseKeepAlive == false) if (args.WebSession.Response.ResponseKeepAlive == false)
{ {
Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
break; break;
} }
...@@ -418,8 +511,9 @@ namespace Titanium.Web.Proxy ...@@ -418,8 +511,9 @@ namespace Titanium.Web.Proxy
httpCmd = await clientStreamReader.ReadLineAsync(); httpCmd = await clientStreamReader.ReadLineAsync();
} }
catch catch (Exception e)
{ {
ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request", e, args));
Dispose(clientStream, clientStreamReader, clientStreamWriter, args); Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
break; break;
} }
...@@ -475,7 +569,6 @@ namespace Titanium.Web.Proxy ...@@ -475,7 +569,6 @@ namespace Titanium.Web.Proxy
webRequest.Request.RequestHeaders = requestHeaders; webRequest.Request.RequestHeaders = requestHeaders;
} }
/// <summary> /// <summary>
/// This is called when the request is PUT/POST to read the body /// This is called when the request is PUT/POST to read the body
/// </summary> /// </summary>
...@@ -497,9 +590,7 @@ namespace Titanium.Web.Proxy ...@@ -497,9 +590,7 @@ namespace Titanium.Web.Proxy
else if (args.WebSession.Request.IsChunked) else if (args.WebSession.Request.IsChunked)
{ {
await args.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(BUFFER_SIZE, postStream); await args.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(BUFFER_SIZE, postStream);
} }
} }
} }
} }
\ No newline at end of file
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Compression; using Titanium.Web.Proxy.Compression;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy
{ namespace Titanium.Web.Proxy
/// <summary> {
/// Handle the response from server /// <summary>
/// </summary> /// Handle the response from server
partial class ProxyServer /// </summary>
{ partial class ProxyServer
//Called asynchronously when a request was successfully and we received the response {
public async Task HandleHttpSessionResponse(SessionEventArgs args) //Called asynchronously when a request was successfully and we received the response
{ public async Task HandleHttpSessionResponse(SessionEventArgs args)
//read response & headers from server {
await args.WebSession.ReceiveResponse(); //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) args.ReRequest = false;
{
Delegate[] invocationList = BeforeResponse.GetInvocationList(); //If user requested call back then do it
Task[] handlerTasks = new Task[invocationList.Length]; if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked)
{
for (int i = 0; i < invocationList.Length; i++) Delegate[] invocationList = BeforeResponse.GetInvocationList();
{ Task[] handlerTasks = new Task[invocationList.Length];
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(null, args);
} for (int i = 0; i < invocationList.Length; i++)
{
await Task.WhenAll(handlerTasks); handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(this, args);
} }
args.WebSession.Response.ResponseLocked = true; await Task.WhenAll(handlerTasks);
}
//Write back to client 100-conitinue response if that's what server returned
if (args.WebSession.Response.Is100Continue) if(args.ReRequest)
{ {
await WriteResponseStatus(args.WebSession.Response.HttpVersion, "100", await HandleHttpSessionRequestInternal(null, args, null, null, true).ConfigureAwait(false);
"Continue", args.ProxyClient.ClientStreamWriter); return;
await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); }
}
else if (args.WebSession.Response.ExpectationFailed) args.WebSession.Response.ResponseLocked = true;
{
await WriteResponseStatus(args.WebSession.Response.HttpVersion, "417", //Write back to client 100-conitinue response if that's what server returned
"Expectation Failed", args.ProxyClient.ClientStreamWriter); if (args.WebSession.Response.Is100Continue)
await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); {
} await WriteResponseStatus(args.WebSession.Response.HttpVersion, "100",
"Continue", args.ProxyClient.ClientStreamWriter);
//Write back response status to client await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
await WriteResponseStatus(args.WebSession.Response.HttpVersion, args.WebSession.Response.ResponseStatusCode, }
args.WebSession.Response.ResponseStatusDescription, args.ProxyClient.ClientStreamWriter); else if (args.WebSession.Response.ExpectationFailed)
{
if (args.WebSession.Response.ResponseBodyRead) await WriteResponseStatus(args.WebSession.Response.HttpVersion, "417",
{ "Expectation Failed", args.ProxyClient.ClientStreamWriter);
var isChunked = args.WebSession.Response.IsChunked; await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
var contentEncoding = args.WebSession.Response.ContentEncoding; }
if (contentEncoding != null) //Write back response status to client
{ await WriteResponseStatus(args.WebSession.Response.HttpVersion, args.WebSession.Response.ResponseStatusCode,
args.WebSession.Response.ResponseBody = await GetCompressedResponseBody(contentEncoding, args.WebSession.Response.ResponseBody); args.WebSession.Response.ResponseStatusDescription, args.ProxyClient.ClientStreamWriter);
if (isChunked == false) if (args.WebSession.Response.ResponseBodyRead)
{ {
args.WebSession.Response.ContentLength = args.WebSession.Response.ResponseBody.Length; var isChunked = args.WebSession.Response.IsChunked;
} var contentEncoding = args.WebSession.Response.ContentEncoding;
else
{ if (contentEncoding != null)
args.WebSession.Response.ContentLength = -1; {
} args.WebSession.Response.ResponseBody = await GetCompressedResponseBody(contentEncoding, args.WebSession.Response.ResponseBody);
}
if (isChunked == false)
await WriteResponseHeaders(args.ProxyClient.ClientStreamWriter, args.WebSession.Response); {
await args.ProxyClient.ClientStream.WriteResponseBody(args.WebSession.Response.ResponseBody, isChunked); args.WebSession.Response.ContentLength = args.WebSession.Response.ResponseBody.Length;
} }
else else
{ {
await WriteResponseHeaders(args.ProxyClient.ClientStreamWriter, args.WebSession.Response); args.WebSession.Response.ContentLength = -1;
}
//Write body only if response is chunked or content length >0 }
//Is none are true then check if connection:close header exist, if so write response until server or client terminates the connection
if (args.WebSession.Response.IsChunked || args.WebSession.Response.ContentLength > 0 await WriteResponseHeaders(args.ProxyClient.ClientStreamWriter, args.WebSession.Response);
|| !args.WebSession.Response.ResponseKeepAlive) await args.ProxyClient.ClientStream.WriteResponseBody(args.WebSession.Response.ResponseBody, isChunked);
{ }
await args.WebSession.ServerConnection.StreamReader else
.WriteResponseBody(BUFFER_SIZE, args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked, {
args.WebSession.Response.ContentLength); await WriteResponseHeaders(args.ProxyClient.ClientStreamWriter, args.WebSession.Response);
}
//write response if connection:keep-alive header exist and when version is http/1.0 //Write body only if response is chunked or content length >0
//Because in Http 1.0 server can return a response without content-length (expectation being client would read until end of stream) //Is none are true then check if connection:close header exist, if so write response until server or client terminates the connection
else if (args.WebSession.Response.ResponseKeepAlive && args.WebSession.Response.HttpVersion.Minor == 0) if (args.WebSession.Response.IsChunked || args.WebSession.Response.ContentLength > 0
{ || !args.WebSession.Response.ResponseKeepAlive)
await args.WebSession.ServerConnection.StreamReader {
.WriteResponseBody(BUFFER_SIZE, args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked, await args.WebSession.ServerConnection.StreamReader
args.WebSession.Response.ContentLength); .WriteResponseBody(BUFFER_SIZE, args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked,
} args.WebSession.Response.ContentLength);
} }
//write response if connection:keep-alive header exist and when version is http/1.0
await args.ProxyClient.ClientStream.FlushAsync(); //Because in Http 1.0 server can return a response without content-length (expectation being client would read until end of stream)
else if (args.WebSession.Response.ResponseKeepAlive && args.WebSession.Response.HttpVersion.Minor == 0)
} {
catch await args.WebSession.ServerConnection.StreamReader
{ .WriteResponseBody(BUFFER_SIZE, args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked,
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.WebSession.Response.ContentLength);
args.ProxyClient.ClientStreamWriter, args); }
} }
finally
{ await args.ProxyClient.ClientStream.FlushAsync();
args.Dispose();
} }
} catch(Exception e)
{
/// <summary> ExceptionFunc(new ProxyHttpException("Error occured wilst handling session response", e, args));
/// get the compressed response body from give response bytes Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader,
/// </summary> args.ProxyClient.ClientStreamWriter, args);
/// <param name="encodingType"></param> }
/// <param name="responseBodyStream"></param> finally
/// <returns></returns> {
private async Task<byte[]> GetCompressedResponseBody(string encodingType, byte[] responseBodyStream) args.Dispose();
{ }
var compressionFactory = new CompressionFactory(); }
var compressor = compressionFactory.Create(encodingType);
return await compressor.Compress(responseBodyStream); /// <summary>
} /// get the compressed response body from give response bytes
/// </summary>
/// <summary> /// <param name="encodingType"></param>
/// Write response status /// <param name="responseBodyStream"></param>
/// </summary> /// <returns></returns>
/// <param name="version"></param> private async Task<byte[]> GetCompressedResponseBody(string encodingType, byte[] responseBodyStream)
/// <param name="code"></param> {
/// <param name="description"></param> var compressionFactory = new CompressionFactory();
/// <param name="responseWriter"></param> var compressor = compressionFactory.Create(encodingType);
/// <returns></returns> return await compressor.Compress(responseBodyStream);
private async Task WriteResponseStatus(Version version, string code, string description, }
StreamWriter responseWriter)
{ /// <summary>
await responseWriter.WriteLineAsync(string.Format("HTTP/{0}.{1} {2} {3}", version.Major, version.Minor, code, description)); /// Write response status
} /// </summary>
/// <param name="version"></param>
/// <summary> /// <param name="code"></param>
/// Write response headers to client /// <param name="description"></param>
/// </summary> /// <param name="responseWriter"></param>
/// <param name="responseWriter"></param> /// <returns></returns>
/// <param name="headers"></param> private async Task WriteResponseStatus(Version version, string code, string description,
/// <returns></returns> StreamWriter responseWriter)
private async Task WriteResponseHeaders(StreamWriter responseWriter, Response response) {
{ await responseWriter.WriteLineAsync(string.Format("HTTP/{0}.{1} {2} {3}", version.Major, version.Minor, code, description));
FixProxyHeaders(response.ResponseHeaders); }
foreach (var header in response.ResponseHeaders) /// <summary>
{ /// Write response headers to client
await responseWriter.WriteLineAsync(header.Value.ToString()); /// </summary>
} /// <param name="responseWriter"></param>
/// <param name="headers"></param>
//write non unique request headers /// <returns></returns>
foreach (var headerItem in response.NonUniqueResponseHeaders) private async Task WriteResponseHeaders(StreamWriter responseWriter, Response response)
{ {
var headers = headerItem.Value; FixProxyHeaders(response.ResponseHeaders);
foreach (var header in headers)
{ foreach (var header in response.ResponseHeaders)
await responseWriter.WriteLineAsync(header.ToString()); {
} await responseWriter.WriteLineAsync(header.Value.ToString());
} }
//write non unique request headers
await responseWriter.WriteLineAsync(); foreach (var headerItem in response.NonUniqueResponseHeaders)
await responseWriter.FlushAsync(); {
} var headers = headerItem.Value;
foreach (var header in headers)
/// <summary> {
/// Fix proxy specific headers await responseWriter.WriteLineAsync(header.ToString());
/// </summary> }
/// <param name="headers"></param> }
private void FixProxyHeaders(Dictionary<string, HttpHeader> headers)
{
//If proxy-connection close was returned inform to close the connection await responseWriter.WriteLineAsync();
var hasProxyHeader = headers.ContainsKey("proxy-connection"); await responseWriter.FlushAsync();
var hasConnectionheader = headers.ContainsKey("connection"); }
if (hasProxyHeader) /// <summary>
{ /// Fix proxy specific headers
var proxyHeader = headers["proxy-connection"]; /// </summary>
if (hasConnectionheader == false) /// <param name="headers"></param>
{ private void FixProxyHeaders(Dictionary<string, HttpHeader> headers)
headers.Add("connection", new HttpHeader("connection", proxyHeader.Value)); {
} //If proxy-connection close was returned inform to close the connection
else var hasProxyHeader = headers.ContainsKey("proxy-connection");
{ var hasConnectionheader = headers.ContainsKey("connection");
var connectionHeader = headers["connection"];
connectionHeader.Value = proxyHeader.Value; if (hasProxyHeader)
} {
var proxyHeader = headers["proxy-connection"];
headers.Remove("proxy-connection"); if (hasConnectionheader == false)
} {
headers.Add("connection", new HttpHeader("connection", proxyHeader.Value));
} }
else
/// <summary> {
/// Handle dispose of a client/server session var connectionHeader = headers["connection"];
/// </summary> connectionHeader.Value = proxyHeader.Value;
/// <param name="tcpClient"></param> }
/// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param> headers.Remove("proxy-connection");
/// <param name="clientStreamWriter"></param> }
/// <param name="args"></param>
private void Dispose(Stream clientStream, CustomBinaryReader clientStreamReader, }
StreamWriter clientStreamWriter, IDisposable args)
{ /// <summary>
/// Handle dispose of a client/server session
if (clientStream != null) /// </summary>
{ /// <param name="tcpClient"></param>
clientStream.Close(); /// <param name="clientStream"></param>
clientStream.Dispose(); /// <param name="clientStreamReader"></param>
} /// <param name="clientStreamWriter"></param>
/// <param name="args"></param>
if (args != null) private void Dispose(Stream clientStream, CustomBinaryReader clientStreamReader,
{ StreamWriter clientStreamWriter, IDisposable args)
args.Dispose(); {
}
if (clientStream != null)
if (clientStreamReader != null) {
{ clientStream.Close();
clientStreamReader.Dispose(); clientStream.Dispose();
} }
if (clientStreamWriter != null) if (args != null)
{ {
clientStreamWriter.Close(); args.Dispose();
clientStreamWriter.Dispose(); }
}
} if (clientStreamReader != null)
} {
clientStreamReader.Dispose();
}
if (clientStreamWriter != null)
{
clientStreamWriter.Close();
clientStreamWriter.Dispose();
}
}
}
} }
\ No newline at end of file
using System.Net;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Tcp
{
/// <summary>
/// Represents a managed interface of IP Helper API TcpRow struct
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary>
internal class TcpRow
{
/// <summary>
/// Initializes a new instance of the <see cref="TcpRow"/> class.
/// </summary>
/// <param name="tcpRow">TcpRow struct.</param>
public TcpRow(NativeMethods.TcpRow tcpRow)
{
ProcessId = tcpRow.owningPid;
int localPort = (tcpRow.localPort1 << 8) + (tcpRow.localPort2) + (tcpRow.localPort3 << 24) + (tcpRow.localPort4 << 16);
long localAddress = tcpRow.localAddr;
LocalEndPoint = new IPEndPoint(localAddress, localPort);
int remotePort = (tcpRow.remotePort1 << 8) + (tcpRow.remotePort2) + (tcpRow.remotePort3 << 24) + (tcpRow.remotePort4 << 16);
long remoteAddress = tcpRow.remoteAddr;
RemoteEndPoint = new IPEndPoint(remoteAddress, remotePort);
}
/// <summary>
/// Gets the local end point.
/// </summary>
public IPEndPoint LocalEndPoint { get; private set; }
/// <summary>
/// Gets the remote end point.
/// </summary>
public IPEndPoint RemoteEndPoint { get; private set; }
/// <summary>
/// Gets the process identifier.
/// </summary>
public int ProcessId { get; private set; }
}
}
\ No newline at end of file
using System.Collections;
using System.Collections.Generic;
namespace Titanium.Web.Proxy.Tcp
{
/// <summary>
/// Represents collection of TcpRows
/// </summary>
/// <seealso cref="System.Collections.Generic.IEnumerable{Titanium.Web.Proxy.Tcp.TcpRow}" />
internal class TcpTable : IEnumerable<TcpRow>
{
private readonly IEnumerable<TcpRow> tcpRows;
/// <summary>
/// Initializes a new instance of the <see cref="TcpTable"/> class.
/// </summary>
/// <param name="tcpRows">TcpRow collection to initialize with.</param>
public TcpTable(IEnumerable<TcpRow> tcpRows)
{
this.tcpRows = tcpRows;
}
/// <summary>
/// Gets the TCP rows.
/// </summary>
public IEnumerable<TcpRow> TcpRows => tcpRows;
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
public IEnumerator<TcpRow> GetEnumerator()
{
return tcpRows.GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
\ No newline at end of file
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<DocumentationFile>bin\Debug\Titanium.Web.Proxy.XML</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
...@@ -32,6 +33,7 @@ ...@@ -32,6 +33,7 @@
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<DefineConstants>NET45</DefineConstants> <DefineConstants>NET45</DefineConstants>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<DocumentationFile>bin\Release\Titanium.Web.Proxy.XML</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Ionic.Zip, Version=1.9.8.0, Culture=neutral, PublicKeyToken=6583c7c814667745, processorArchitecture=MSIL"> <Reference Include="Ionic.Zip, Version=1.9.8.0, Culture=neutral, PublicKeyToken=6583c7c814667745, processorArchitecture=MSIL">
...@@ -64,9 +66,14 @@ ...@@ -64,9 +66,14 @@
<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="Extensions\ByteArrayExtensions.cs" />
<Compile Include="Helpers\Network.cs" />
<Compile Include="Network\CachedCertificate.cs" /> <Compile Include="Network\CachedCertificate.cs" />
<Compile Include="Network\CertificateMaker.cs" />
<Compile Include="Network\ProxyClient.cs" /> <Compile Include="Network\ProxyClient.cs" />
<Compile Include="Exceptions\BodyNotFoundException.cs" /> <Compile Include="Exceptions\BodyNotFoundException.cs" />
<Compile Include="Exceptions\ProxyAuthorizationException.cs" />
<Compile Include="Exceptions\ProxyException.cs" />
<Compile Include="Exceptions\ProxyHttpException.cs" />
<Compile Include="Extensions\HttpWebResponseExtensions.cs" /> <Compile Include="Extensions\HttpWebResponseExtensions.cs" />
<Compile Include="Extensions\HttpWebRequestExtensions.cs" /> <Compile Include="Extensions\HttpWebRequestExtensions.cs" />
<Compile Include="Network\CertificateManager.cs" /> <Compile Include="Network\CertificateManager.cs" />
...@@ -82,6 +89,7 @@ ...@@ -82,6 +89,7 @@
<Compile Include="Models\HttpHeader.cs" /> <Compile Include="Models\HttpHeader.cs" />
<Compile Include="Http\HttpWebClient.cs" /> <Compile Include="Http\HttpWebClient.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ProxyAuthorizationHandler.cs" />
<Compile Include="RequestHandler.cs" /> <Compile Include="RequestHandler.cs" />
<Compile Include="ResponseHandler.cs" /> <Compile Include="ResponseHandler.cs" />
<Compile Include="Helpers\CustomBinaryReader.cs" /> <Compile Include="Helpers\CustomBinaryReader.cs" />
...@@ -92,16 +100,23 @@ ...@@ -92,16 +100,23 @@
<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\ProxyConstants.cs" /> <Compile Include="Shared\ProxyConstants.cs" />
<Compile Include="Tcp\TcpRow.cs" />
<Compile Include="Tcp\TcpTable.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup />
<ItemGroup> <ItemGroup>
<None Include="app.config" /> <COMReference Include="CERTENROLLLib">
<None Include="packages.config" /> <Guid>{728AB348-217D-11DA-B2A4-000E7BBB2B09}</Guid>
<VersionMajor>1</VersionMajor>
<VersionMinor>0</VersionMinor>
<Lcid>0</Lcid>
<WrapperTool>tlbimp</WrapperTool>
<Isolated>False</Isolated>
<EmbedInteropTypes>True</EmbedInteropTypes>
</COMReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="makecert.exe"> <None Include="app.config" />
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <None Include="packages.config" />
</None>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" /> <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
......
...@@ -19,6 +19,5 @@ ...@@ -19,6 +19,5 @@
</metadata> </metadata>
<files> <files>
<file src="bin\$configuration$\Titanium.Web.Proxy.dll" target="lib\net45" /> <file src="bin\$configuration$\Titanium.Web.Proxy.dll" target="lib\net45" />
<file src="bin\$configuration$\makecert.exe" target="content" />
</files> </files>
</package> </package>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment