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,7 +34,7 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -33,7 +34,7 @@ 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,9 +60,10 @@ namespace Titanium.Web.Proxy ...@@ -60,9 +60,10 @@ 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,
...@@ -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.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using Titanium.Web.Proxy.Exceptions; using Titanium.Web.Proxy.Exceptions;
...@@ -9,6 +10,7 @@ using Titanium.Web.Proxy.Extensions; ...@@ -9,6 +10,7 @@ using Titanium.Web.Proxy.Extensions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Network;
using System.Net; using System.Net;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
...@@ -36,6 +38,13 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -36,6 +38,13 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
internal ProxyClient ProxyClient { get; set; } internal ProxyClient ProxyClient { get; set; }
//Should we send a rerequest
public bool ReRequest
{
get;
set;
}
/// <summary> /// <summary>
/// Does this session uses SSL /// Does this session uses SSL
/// </summary> /// </summary>
...@@ -50,6 +59,9 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -50,6 +59,9 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
public HttpWebClient WebSession { get; set; } public HttpWebClient WebSession { get; set; }
public ExternalProxy CustomUpStreamHttpProxyUsed { get; set; }
public ExternalProxy CustomUpStreamHttpsProxyUsed { get; set; }
/// <summary> /// <summary>
/// Constructor to initialize the proxy /// Constructor to initialize the proxy
...@@ -138,7 +150,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -138,7 +150,7 @@ namespace Titanium.Web.Proxy.EventArguments
WebSession.Response.ContentLength); WebSession.Response.ContentLength);
} }
else if (WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0) else if ((WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0) || WebSession.Response.ContentLength == -1)
{ {
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream, long.MaxValue); await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream, long.MaxValue);
} }
...@@ -331,7 +343,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -331,7 +343,6 @@ namespace Titanium.Web.Proxy.EventArguments
return await decompressor.Decompress(responseBodyStream, bufferSize); return await decompressor.Decompress(responseBodyStream, bufferSize);
} }
/// <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 HTML string to client
...@@ -339,6 +350,18 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -339,6 +350,18 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
/// <param name="html"></param> /// <param name="html"></param>
public async Task Ok(string html) public async Task Ok(string html)
{
await Ok(html, null);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified HTML string to client
/// and ignore the request
/// </summary>
/// <param name="html"></param>
/// <param name="headers"></param>
public async Task Ok(string html, Dictionary<string, HttpHeader> headers)
{ {
if (WebSession.Request.RequestLocked) if (WebSession.Request.RequestLocked)
{ {
...@@ -352,7 +375,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -352,7 +375,7 @@ namespace Titanium.Web.Proxy.EventArguments
var result = Encoding.Default.GetBytes(html); var result = Encoding.Default.GetBytes(html);
await Ok(result); await Ok(result, headers);
} }
/// <summary> /// <summary>
...@@ -360,11 +383,26 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -360,11 +383,26 @@ namespace Titanium.Web.Proxy.EventArguments
/// Respond with the specified byte[] to client /// Respond with the specified byte[] to client
/// and ignore the request /// and ignore the request
/// </summary> /// </summary>
/// <param name="body"></param> /// <param name="result"></param>
public async Task Ok(byte[] result) public async Task Ok(byte[] result)
{ {
var response = new OkResponse(); await Ok(result, null);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified byte[] to client
/// and ignore the request
/// </summary>
/// <param name="result"></param>
/// <param name="headers"></param>
public async Task Ok(byte[] result, Dictionary<string, HttpHeader> headers)
{
var response = new OkResponse();
if (headers != null && headers.Count > 0)
{
response.ResponseHeaders = headers;
}
response.HttpVersion = WebSession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseBody = result; response.ResponseBody = result;
......
...@@ -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,10 +29,12 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -29,10 +29,12 @@ namespace Titanium.Web.Proxy.Extensions
await input.CopyToAsync(output); await input.CopyToAsync(output);
} }
/// <summary> /// <summary>
/// copies the specified bytes to the stream from the input stream /// copies the specified bytes to the stream from the input stream
/// </summary> /// </summary>
/// <param name="streamReader"></param> /// <param name="streamReader"></param>
/// <param name="bufferSize"></param>
/// <param name="stream"></param> /// <param name="stream"></param>
/// <param name="totalBytesToRead"></param> /// <param name="totalBytesToRead"></param>
/// <returns></returns> /// <returns></returns>
...@@ -40,16 +42,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -40,16 +42,7 @@ namespace Titanium.Web.Proxy.Extensions
{ {
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)
{ {
...@@ -76,6 +69,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -76,6 +69,7 @@ namespace Titanium.Web.Proxy.Extensions
/// Copies the stream chunked /// Copies the stream chunked
/// </summary> /// </summary>
/// <param name="clientStreamReader"></param> /// <param name="clientStreamReader"></param>
/// <param name="bufferSize"></param>
/// <param name="stream"></param> /// <param name="stream"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, int bufferSize, Stream stream) internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, int bufferSize, Stream stream)
...@@ -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> /// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream /// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// optionally chunked /// optionally chunked
/// </summary> /// </summary>
/// <param name="inStreamReader"></param> /// <param name="inStreamReader"></param>
/// <param name="bufferSize"></param>
/// <param name="outStream"></param> /// <param name="outStream"></param>
/// <param name="isChunked"></param> /// <param name="isChunked"></param>
/// <param name="ContentLength"></param> /// <param name="contentLength"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task WriteResponseBody(this CustomBinaryReader inStreamReader, int bufferSize, Stream outStream, bool isChunked, long ContentLength) 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;
} }
} }
...@@ -171,6 +167,7 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -171,6 +167,7 @@ namespace Titanium.Web.Proxy.Extensions
/// Copies the streams chunked /// Copies the streams chunked
/// </summary> /// </summary>
/// <param name="inStreamReader"></param> /// <param name="inStreamReader"></param>
/// <param name="bufferSize"></param>
/// <param name="outStream"></param> /// <param name="outStream"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task WriteResponseBodyChunked(this CustomBinaryReader inStreamReader, int bufferSize, Stream outStream) internal static async Task WriteResponseBodyChunked(this CustomBinaryReader inStreamReader, int bufferSize, Stream outStream)
......
...@@ -36,8 +36,6 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -36,8 +36,6 @@ namespace Titanium.Web.Proxy.Helpers
internal async Task<string> ReadLineAsync() internal async Task<string> ReadLineAsync()
{ {
using (var readBuffer = new MemoryStream()) using (var readBuffer = new MemoryStream())
{
try
{ {
var lastChar = default(char); var lastChar = default(char);
var buffer = new byte[1]; var buffer = new byte[1];
...@@ -64,11 +62,6 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -64,11 +62,6 @@ namespace Titanium.Web.Proxy.Helpers
return encoding.GetString(readBuffer.ToArray()); return encoding.GetString(readBuffer.ToArray());
} }
catch (IOException)
{
throw;
}
}
} }
/// <summary> /// <summary>
......
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
{
Http,
Https,
}
internal class NativeMethods 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>
...@@ -46,28 +46,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -46,28 +46,7 @@ namespace Titanium.Web.Proxy.Helpers
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>
...@@ -75,31 +54,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -75,31 +54,7 @@ namespace Titanium.Web.Proxy.Helpers
/// </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>
...@@ -108,60 +63,65 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -108,60 +63,65 @@ namespace Titanium.Web.Proxy.Helpers
/// <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); var existingSystemProxyValues = GetSystemProxyValues(exisitingContent);
existingSystemProxyValues.RemoveAll(x => x.IsHttps); existingSystemProxyValues.RemoveAll(x => protocolType == ProxyProtocolType.Https ? x.IsHttps : !x.IsHttps);
if (!(existingSystemProxyValues.Count() == 0)) if (existingSystemProxyValues.Count != 0)
{ {
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()));
} }
else else
{ {
reg.SetValue("ProxyEnable", 0); reg.SetValue("ProxyEnable", 0);
reg.SetValue("ProxyServer", string.Empty); reg.SetValue("ProxyServer", string.Empty);
} }
}
} }
Refresh(); Refresh();
...@@ -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)
{ {
......
...@@ -2,20 +2,124 @@ ...@@ -2,20 +2,124 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.NetworkInformation;
using System.Net.Security; using System.Net.Security;
using System.Net.Sockets; using System.Runtime.InteropServices;
using System.Security.Authentication; using System.Security.Authentication;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Tcp;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
internal enum IpVersion
{
Ipv4 = 1,
Ipv6 = 2,
}
internal partial class NativeMethods
{
internal const int AfInet = 2;
internal const int AfInet6 = 23;
internal enum TcpTableType
{
BasicListener,
BasicConnections,
BasicAll,
OwnerPidListener,
OwnerPidConnections,
OwnerPidAll,
OwnerModuleListener,
OwnerModuleConnections,
OwnerModuleAll,
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366921.aspx"/>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct TcpTable
{
public uint length;
public TcpRow row;
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct TcpRow
{
public TcpState state;
public uint localAddr;
public byte localPort1;
public byte localPort2;
public byte localPort3;
public byte localPort4;
public uint remoteAddr;
public byte remotePort1;
public byte remotePort2;
public byte remotePort3;
public byte remotePort4;
public int owningPid;
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/>
/// </summary>
[DllImport("iphlpapi.dll", SetLastError = true)]
internal static extern uint GetExtendedTcpTable(IntPtr tcpTable, ref int size, bool sort, int ipVersion, int tableClass, int reserved);
}
internal class TcpHelper internal class TcpHelper
{ {
/// <summary>
/// Gets the extended TCP table.
/// </summary>
/// <returns>Collection of <see cref="TcpRow"/>.</returns>
internal static TcpTable GetExtendedTcpTable(IpVersion ipVersion)
{
List<TcpRow> tcpRows = new List<TcpRow>();
IntPtr tcpTable = IntPtr.Zero;
int tcpTableLength = 0;
var ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6;
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) != 0)
{
try
{
tcpTable = Marshal.AllocHGlobal(tcpTableLength);
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) == 0)
{
NativeMethods.TcpTable table = (NativeMethods.TcpTable)Marshal.PtrToStructure(tcpTable, typeof(NativeMethods.TcpTable));
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> /// <summary>
/// relays the input clientStream to the server at the specified host name & port with the given httpCmd & headers as prefix /// relays the input clientStream to the server at the specified host name & port with the given httpCmd & headers as prefix
...@@ -73,36 +177,19 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -73,36 +177,19 @@ namespace Titanium.Web.Proxy.Helpers
try try
{ {
TcpClient tunnelClient = tcpConnection.TcpClient;
Stream tunnelStream = tcpConnection.Stream; Stream tunnelStream = tcpConnection.Stream;
Task sendRelay;
//Now async relay all server=>client & client=>server data //Now async relay all server=>client & client=>server data
if (sb != null) var sendRelay = clientStream.CopyToAsync(sb?.ToString() ?? string.Empty, tunnelStream);
{
sendRelay = clientStream.CopyToAsync(sb.ToString(), tunnelStream);
}
else
{
sendRelay = clientStream.CopyToAsync(string.Empty, tunnelStream);
}
var receiveRelay = tunnelStream.CopyToAsync(string.Empty, clientStream); var receiveRelay = tunnelStream.CopyToAsync(string.Empty, clientStream);
await Task.WhenAll(sendRelay, receiveRelay); await Task.WhenAll(sendRelay, receiveRelay);
} }
catch
{
throw;
}
finally finally
{ {
tcpConnection.Dispose(); tcpConnection.Dispose();
} }
} }
} }
} }
\ No newline at end of file
...@@ -14,39 +14,49 @@ namespace Titanium.Web.Proxy.Http ...@@ -14,39 +14,49 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
public class HttpWebClient public class HttpWebClient
{ {
/// <summary> /// <summary>
/// Connection to server /// Connection to server
/// </summary> /// </summary>
internal TcpConnection ServerConnection { get; set; } internal TcpConnection ServerConnection { get; set; }
public Guid RequestId { get; private set; }
public List<HttpHeader> ConnectHeaders { get; set; }
public Request Request { get; set; } public Request Request { get; set; }
public Response Response { get; set; } public Response Response { get; set; }
/// <summary>
/// PID of the process that is created the current session when client is running in this machine
/// If client is remote then this will return
/// </summary>
public Lazy<int> ProcessId { get; internal set; }
/// <summary> /// <summary>
/// Is Https? /// Is Https?
/// </summary> /// </summary>
public bool IsHttps public bool IsHttps => this.Request.RequestUri.Scheme == Uri.UriSchemeHttps;
{
get
internal HttpWebClient()
{ {
return this.Request.RequestUri.Scheme == Uri.UriSchemeHttps; this.RequestId = Guid.NewGuid();
}
this.Request = new Request();
this.Response = new Response();
} }
/// <summary> /// <summary>
/// Set the tcp connection to server used by this webclient /// Set the tcp connection to server used by this webclient
/// </summary> /// </summary>
/// <param name="Connection"></param> /// <param name="connection">Instance of <see cref="TcpConnection"/></param>
internal void SetConnection(TcpConnection Connection) internal void SetConnection(TcpConnection connection)
{ {
ServerConnection = Connection; connection.LastAccess = DateTime.Now;
ServerConnection = connection;
} }
internal HttpWebClient()
{
this.Request = new Request();
this.Response = new Response();
}
/// <summary> /// <summary>
/// Prepare & send the http(s) request /// Prepare & send the http(s) request
...@@ -59,34 +69,49 @@ namespace Titanium.Web.Proxy.Http ...@@ -59,34 +69,49 @@ namespace Titanium.Web.Proxy.Http
StringBuilder requestLines = new StringBuilder(); StringBuilder requestLines = new StringBuilder();
//prepare the request & headers //prepare the request & headers
requestLines.AppendLine(string.Join(" ", new string[3] if ((ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false) || (ServerConnection.UpStreamHttpsProxy != null && ServerConnection.IsHttps == true))
{
requestLines.AppendLine(string.Join(" ", this.Request.Method, this.Request.RequestUri.AbsoluteUri, $"HTTP/{this.Request.HttpVersion.Major}.{this.Request.HttpVersion.Minor}"));
}
else
{ {
this.Request.Method, requestLines.AppendLine(string.Join(" ", this.Request.Method, this.Request.RequestUri.PathAndQuery, $"HTTP/{this.Request.HttpVersion.Major}.{this.Request.HttpVersion.Minor}"));
this.Request.RequestUri.PathAndQuery, }
string.Format("HTTP/{0}.{1}",this.Request.HttpVersion.Major, this.Request.HttpVersion.Minor)
}));
//Send Authentication to Upstream proxy if needed
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)));
}
//write request headers //write request headers
foreach (var headerItem in this.Request.RequestHeaders) foreach (var headerItem in this.Request.RequestHeaders)
{ {
var header = headerItem.Value; var header = headerItem.Value;
if (headerItem.Key != "Proxy-Authorization")
{
requestLines.AppendLine(header.Name + ':' + header.Value); requestLines.AppendLine(header.Name + ':' + header.Value);
} }
}
//write non unique request headers //write non unique request headers
foreach (var headerItem in this.Request.NonUniqueRequestHeaders) foreach (var headerItem in this.Request.NonUniqueRequestHeaders)
{ {
var headers = headerItem.Value; var headers = headerItem.Value;
foreach (var header in headers) foreach (var header in headers)
{
if (headerItem.Key != "Proxy-Authorization")
{ {
requestLines.AppendLine(header.Name + ':' + header.Value); requestLines.AppendLine(header.Name + ':' + header.Value);
} }
} }
}
requestLines.AppendLine(); requestLines.AppendLine();
string request = requestLines.ToString(); string request = requestLines.ToString();
byte[] requestBytes = Encoding.ASCII.GetBytes(request); byte[] requestBytes = Encoding.ASCII.GetBytes(request);
await stream.WriteAsync(requestBytes, 0, requestBytes.Length); await stream.WriteAsync(requestBytes, 0, requestBytes.Length);
await stream.FlushAsync(); await stream.FlushAsync();
...@@ -187,10 +212,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -187,10 +212,7 @@ namespace Titanium.Web.Proxy.Http
{ {
var existing = Response.ResponseHeaders[newHeader.Name]; var existing = Response.ResponseHeaders[newHeader.Name];
var nonUniqueHeaders = new List<HttpHeader>(); var nonUniqueHeaders = new List<HttpHeader> {existing, newHeader};
nonUniqueHeaders.Add(existing);
nonUniqueHeaders.Add(newHeader);
Response.NonUniqueResponseHeaders.Add(newHeader.Name, nonUniqueHeaders); Response.NonUniqueResponseHeaders.Add(newHeader.Name, nonUniqueHeaders);
Response.ResponseHeaders.Remove(newHeader.Name); Response.ResponseHeaders.Remove(newHeader.Name);
...@@ -203,5 +225,4 @@ namespace Titanium.Web.Proxy.Http ...@@ -203,5 +225,4 @@ namespace Titanium.Web.Proxy.Http
} }
} }
} }
} }
\ 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,12 +233,13 @@ namespace Titanium.Web.Proxy.Http ...@@ -234,12 +233,13 @@ 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>
......
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,7 +15,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -16,7 +15,7 @@ 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
...@@ -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()
{ {
......
...@@ -20,6 +20,10 @@ namespace Titanium.Web.Proxy.Models ...@@ -20,6 +20,10 @@ namespace Titanium.Web.Proxy.Models
public int Port { get; internal set; } public int Port { get; internal set; }
public bool EnableSsl { get; internal set; } public bool EnableSsl { get; internal set; }
public bool IpV6Enabled => IpAddress == IPAddress.IPv6Any
|| IpAddress == IPAddress.IPv6Loopback
|| IpAddress == IPAddress.IPv6None;
internal TcpListener listener { get; set; } internal TcpListener listener { get; set; }
} }
......
namespace Titanium.Web.Proxy.Models using System;
using System.Net;
namespace Titanium.Web.Proxy.Models
{ {
/// <summary> /// <summary>
/// An upstream proxy this proxy uses if any /// An upstream proxy this proxy uses if any
/// </summary> /// </summary>
public class ExternalProxy public class ExternalProxy
{ {
private static readonly Lazy<NetworkCredential> DefaultCredentials = new Lazy<NetworkCredential>(() => CredentialCache.DefaultNetworkCredentials);
private 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 string HostName { get; set; }
public int Port { 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)
{ {
this.exceptionFunc = exceptionFunc;
certEngine = new CertificateMaker();
Issuer = issuer; Issuer = issuer;
RootCertificateName = rootCertificateName; RootCertificateName = rootCertificateName;
MyStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); certificateCache = new ConcurrentDictionary<string, CachedCertificate>();
RootStore = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
certificateCache = new Dictionary<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);
if (File.Exists(fileName))
{
try
{
return new X509Certificate2(fileName, string.Empty, X509KeyStorageFlags.Exportable);
return rootCertificate != null; }
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) rootCertificate = GetRootCertificate();
if (rootCertificate != null)
{ {
return FindCertificates(MyStore, certificateSubject); return true;
} }
protected virtual X509Certificate2Collection FindCertificates(X509Store store, string certificateSubject) try
{ {
X509Certificate2Collection discoveredCertificates = store.Certificates rootCertificate = CreateCertificate(RootCertificateName, true);
.Find(X509FindType.FindBySubjectDistinguishedName, certificateSubject, false);
return discoveredCertificates.Count > 0 ?
discoveredCertificates : null;
} }
catch(Exception e)
internal async Task<X509Certificate2> CreateCertificate(string certificateName, bool isRootCertificate)
{ {
return await CreateCertificate(MyStore, certificateName, isRootCertificate); 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;
} }
}
X509Certificate2 certificate = null; catch
store.Open(OpenFlags.ReadWrite);
string certificateSubject = string.Format("CN={0}, O={1}", certificateName, Issuer);
X509Certificate2Collection certificates;
if (isRootCertificate)
{ {
certificates = FindCertificates(store, certificateSubject);
if (certificates != null)
{
certificate = certificates[0];
}
} }
X509Certificate2 certificate = null;
if (certificate == null) lock (string.Intern(certificateName))
{ {
string[] args = new[] { if (certificateCache.ContainsKey(certificateName) == false)
GetCertificateCreateArgs(store, certificateName) };
await CreateCertificate(args);
certificates = FindCertificates(store, certificateSubject);
//remove it from store
if (!isRootCertificate)
{ {
DestroyCertificate(certificateName); try
}
if (certificates != null)
{ {
certificate = certificates[0]; certificate = certEngine.MakeCertificate(certificateName, isRootCertificate, rootCertificate);
} }
catch(Exception e)
{
exceptionFunc(e);
} }
store.Close();
if (certificate != null && !certificateCache.ContainsKey(certificateName)) if (certificate != null && !certificateCache.ContainsKey(certificateName))
{ {
certificateCache.Add(certificateName, new CachedCertificate() { Certificate = certificate }); certificateCache.Add(certificateName, new CachedCertificate() { Certificate = certificate });
} }
return certificate;
} }
finally else
{ {
semaphoreLock.Release(); if (certificateCache.ContainsKey(certificateName))
}
}
/// <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) var cached = certificateCache[certificateName];
//but I'm not sure about the guarantees of the Exited event in such a case cached.LastAccess = DateTime.Now;
throw new InvalidOperationException("Could not start process: " + process); return cached.Certificate;
}
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);
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, return certificate;
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,8 +178,8 @@ namespace Titanium.Web.Proxy.Network ...@@ -292,8 +178,8 @@ 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
...@@ -301,17 +187,40 @@ namespace Titanium.Web.Proxy.Network ...@@ -301,17 +187,40 @@ namespace Titanium.Web.Proxy.Network
} }
} }
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
{ {
RootStore.Close(); x509RootStore.Close();
x509PersonalStore.Close();
} }
return true;
}
catch
{
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
{ {
...@@ -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();
...@@ -122,12 +127,15 @@ namespace Titanium.Web.Proxy.Network ...@@ -122,12 +127,15 @@ namespace Titanium.Web.Proxy.Network
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,6 +492,7 @@ namespace Titanium.Web.Proxy ...@@ -378,6 +492,7 @@ 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 () =>
...@@ -390,6 +505,8 @@ namespace Titanium.Web.Proxy ...@@ -390,6 +505,8 @@ namespace Titanium.Web.Proxy
} }
else else
{ {
await HandleClient(endPoint as ExplicitProxyEndPoint, tcpClient); await HandleClient(endPoint as ExplicitProxyEndPoint, tcpClient);
} }
...@@ -398,12 +515,17 @@ namespace Titanium.Web.Proxy ...@@ -398,12 +515,17 @@ namespace Titanium.Web.Proxy
{ {
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
{ {
...@@ -179,11 +200,8 @@ namespace Titanium.Web.Proxy ...@@ -179,11 +200,8 @@ 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
...@@ -5,6 +5,7 @@ using Titanium.Web.Proxy.EventArguments; ...@@ -5,6 +5,7 @@ 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.Exceptions;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
...@@ -29,6 +30,8 @@ namespace Titanium.Web.Proxy ...@@ -29,6 +30,8 @@ namespace Titanium.Web.Proxy
args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream; args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream;
} }
args.ReRequest = false;
//If user requested call back then do it //If user requested call back then do it
if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked) if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked)
{ {
...@@ -37,12 +40,18 @@ namespace Titanium.Web.Proxy ...@@ -37,12 +40,18 @@ namespace Titanium.Web.Proxy
for (int i = 0; i < invocationList.Length; i++) for (int i = 0; i < invocationList.Length; i++)
{ {
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(null, args); handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(this, args);
} }
await Task.WhenAll(handlerTasks); await Task.WhenAll(handlerTasks);
} }
if(args.ReRequest)
{
await HandleHttpSessionRequestInternal(null, args, null, null, true).ConfigureAwait(false);
return;
}
args.WebSession.Response.ResponseLocked = true; args.WebSession.Response.ResponseLocked = true;
//Write back to client 100-conitinue response if that's what server returned //Write back to client 100-conitinue response if that's what server returned
...@@ -111,8 +120,9 @@ namespace Titanium.Web.Proxy ...@@ -111,8 +120,9 @@ namespace Titanium.Web.Proxy
await args.ProxyClient.ClientStream.FlushAsync(); await args.ProxyClient.ClientStream.FlushAsync();
} }
catch catch(Exception e)
{ {
ExceptionFunc(new ProxyHttpException("Error occured wilst handling session response", e, args));
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter, args); args.ProxyClient.ClientStreamWriter, args);
} }
......
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