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
public ProxyTestController()
{
proxyServer = new ProxyServer();
proxyServer.TrustRootCertificate = true;
}
public void StartProxy()
......@@ -115,6 +116,9 @@ namespace Titanium.Web.Proxy.Examples.Basic
//read response headers
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.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
* Safely relays WebSocket requests over Http
* Support mutual SSL authentication
* Fully asynchronous proxy
* Supports proxy authentication
Usage
=====
......@@ -25,18 +27,22 @@ Refer the HTTP Proxy Server library in your project, look up Test project to lea
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:
```csharp
var proxyServer = new ProxyServer();
//locally trust root certificate used by this proxy
proxyServer.TrustRootCertificate = true;
proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse;
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
......@@ -69,8 +75,8 @@ Setup HTTP proxy:
};
proxyServer.AddEndPoint(transparentEndPoint);
//proxyServer.ExternalHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//proxyServer.ExternalHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//proxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//proxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
foreach (var endPoint in proxyServer.ProxyEndPoints)
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
......@@ -176,7 +182,10 @@ Sample request and response event handlers
```
Future roadmap
============
* Implement Kerberos/NTLM authentication over HTTP protocols for windows domain
* Support Server Name Indication (SNI) for transparent endpoints
* 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
{
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);
......@@ -33,9 +34,9 @@ namespace Titanium.Web.Proxy.UnitTests
await Task.Delay(random.Next(0, 10) * 1000);
//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 @@
<ItemGroup>
<Compile Include="CertificateManagerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ProxyServerTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
......
......@@ -60,11 +60,12 @@ namespace Titanium.Web.Proxy
/// Call back to select client certificate used for mutual authentication
/// </summary>
/// <param name="sender"></param>
/// <param name="certificate"></param>
/// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param>
/// <param name="targetHost"></param>
/// <param name="localCertificates"></param>
/// <param name="remoteCertificate"></param>
/// <param name="acceptableIssuers"></param>
/// <returns></returns>
internal X509Certificate SelectClientCertificate(
internal X509Certificate SelectClientCertificate(
object sender,
string targetHost,
X509CertificateCollection localCertificates,
......@@ -101,11 +102,11 @@ namespace Titanium.Web.Proxy
{
var args = new CertificateSelectionEventArgs();
args.targetHost = targetHost;
args.localCertificates = localCertificates;
args.remoteCertificate = remoteCertificate;
args.acceptableIssuers = acceptableIssuers;
args.clientCertificate = clientCertificate;
args.TargetHost = targetHost;
args.LocalCertificates = localCertificates;
args.RemoteCertificate = remoteCertificate;
args.AcceptableIssuers = acceptableIssuers;
args.ClientCertificate = clientCertificate;
Delegate[] invocationList = ClientCertificateSelectionCallback.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length];
......@@ -117,7 +118,7 @@ namespace Titanium.Web.Proxy
Task.WhenAll(handlerTasks).Wait();
return args.clientCertificate;
return args.ClientCertificate;
}
return clientCertificate;
......
......@@ -6,19 +6,15 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary>
/// An argument passed on to user for client certificate selection during mutual SSL authentication
/// </summary>
public class CertificateSelectionEventArgs : EventArgs, IDisposable
public class CertificateSelectionEventArgs : EventArgs
{
public object sender { get; internal set; }
public string targetHost { get; internal set; }
public X509CertificateCollection localCertificates { get; internal set; }
public X509Certificate remoteCertificate { get; internal set; }
public string[] acceptableIssuers { get; internal set; }
public object Sender { get; internal set; }
public string TargetHost { get; internal set; }
public X509CertificateCollection LocalCertificates { get; internal set; }
public X509Certificate RemoteCertificate { get; internal set; }
public string[] AcceptableIssuers { get; internal set; }
public X509Certificate clientCertificate { get; set; }
public X509Certificate ClientCertificate { get; set; }
public void Dispose()
{
throw new NotImplementedException();
}
}
}
......@@ -5,7 +5,7 @@ namespace Titanium.Web.Proxy.Exceptions
/// <summary>
/// An expception thrown when body is unexpectedly empty
/// </summary>
public class BodyNotFoundException : Exception
public class BodyNotFoundException : ProxyException
{
public BodyNotFoundException(string message)
: base(message)
......
using System;
using System.Collections.Generic;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Exceptions
{
/// <summary>
/// Proxy authorization exception
/// </summary>
public class ProxyAuthorizationException : ProxyException
{
/// <summary>
/// Instantiate new instance
/// </summary>
/// <param name="message">Exception message</param>
/// <param name="innerException">Inner exception associated to upstream proxy authorization</param>
/// <param name="headers">Http's headers associated</param>
public ProxyAuthorizationException(string message, Exception innerException, IEnumerable<HttpHeader> headers) : base(message, innerException)
{
Headers = headers;
}
/// <summary>
/// Headers associated with the authorization exception
/// </summary>
public IEnumerable<HttpHeader> Headers { get; }
}
}
\ No newline at end of file
using System;
namespace Titanium.Web.Proxy.Exceptions
{
/// <summary>
/// Base class exception associated with this proxy implementation
/// </summary>
public abstract class ProxyException : Exception
{
/// <summary>
/// Instantiate a new instance of this exception - must be invoked by derived classes' constructors
/// </summary>
/// <param name="message">Exception message</param>
protected ProxyException(string message) : base(message)
{
}
/// <summary>
/// Instantiate this exception - must be invoked by derived classes' constructors
/// </summary>
/// <param name="message">Excception message</param>
/// <param name="innerException">Inner exception associated</param>
protected ProxyException(string message, Exception innerException) : base(message, innerException)
{
}
}
}
\ No newline at end of file
using System;
using Titanium.Web.Proxy.EventArguments;
namespace Titanium.Web.Proxy.Exceptions
{
/// <summary>
/// Proxy HTTP exception
/// </summary>
public class ProxyHttpException : ProxyException
{
/// <summary>
/// Instantiate new instance
/// </summary>
/// <param name="message">Message for this exception</param>
/// <param name="innerException">Associated inner exception</param>
/// <param name="sessionEventArgs">Instance of <see cref="EventArguments.SessionEventArgs"/> associated to the exception</param>
public ProxyHttpException(string message, Exception innerException, SessionEventArgs sessionEventArgs) : base(message, innerException)
{
SessionEventArgs = sessionEventArgs;
}
/// <summary>
/// Gets session info associated to the exception
/// </summary>
/// <remarks>
/// This object should not be edited
/// </remarks>
public SessionEventArgs SessionEventArgs { get; }
}
}
\ No newline at end of file
......@@ -29,27 +29,20 @@ namespace Titanium.Web.Proxy.Extensions
await input.CopyToAsync(output);
}
/// <summary>
/// copies the specified bytes to the stream from the input stream
/// </summary>
/// <param name="streamReader"></param>
/// <param name="stream"></param>
/// <param name="totalBytesToRead"></param>
/// <returns></returns>
internal static async Task CopyBytesToStream(this CustomBinaryReader streamReader, int bufferSize, Stream stream, long totalBytesToRead)
/// <summary>
/// copies the specified bytes to the stream from the input stream
/// </summary>
/// <param name="streamReader"></param>
/// <param name="bufferSize"></param>
/// <param name="stream"></param>
/// <param name="totalBytesToRead"></param>
/// <returns></returns>
internal static async Task CopyBytesToStream(this CustomBinaryReader streamReader, int bufferSize, Stream stream, long totalBytesToRead)
{
var totalbytesRead = 0;
long bytesToRead;
if (totalBytesToRead < bufferSize)
{
bytesToRead = totalBytesToRead;
}
else
{
bytesToRead = bufferSize;
}
long bytesToRead = totalBytesToRead < bufferSize ? totalBytesToRead : bufferSize;
while (totalbytesRead < totalBytesToRead)
{
......@@ -72,13 +65,14 @@ namespace Titanium.Web.Proxy.Extensions
}
}
/// <summary>
/// Copies the stream chunked
/// </summary>
/// <param name="clientStreamReader"></param>
/// <param name="stream"></param>
/// <returns></returns>
internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, int bufferSize, Stream stream)
/// <summary>
/// Copies the stream chunked
/// </summary>
/// <param name="clientStreamReader"></param>
/// <param name="bufferSize"></param>
/// <param name="stream"></param>
/// <returns></returns>
internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, int bufferSize, Stream stream)
{
while (true)
{
......@@ -117,30 +111,32 @@ namespace Titanium.Web.Proxy.Extensions
await WriteResponseBodyChunked(data, clientStream);
}
}
/// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// optionally chunked
/// </summary>
/// <param name="inStreamReader"></param>
/// <param name="outStream"></param>
/// <param name="isChunked"></param>
/// <param name="ContentLength"></param>
/// <returns></returns>
internal static async Task WriteResponseBody(this CustomBinaryReader inStreamReader, int bufferSize, Stream outStream, bool isChunked, long ContentLength)
/// <summary>
/// Copies the specified content length number of bytes to the output stream from the given inputs stream
/// optionally chunked
/// </summary>
/// <param name="inStreamReader"></param>
/// <param name="bufferSize"></param>
/// <param name="outStream"></param>
/// <param name="isChunked"></param>
/// <param name="contentLength"></param>
/// <returns></returns>
internal static async Task WriteResponseBody(this CustomBinaryReader inStreamReader, int bufferSize, Stream outStream, bool isChunked, long contentLength)
{
if (!isChunked)
{
//http 1.0
if (ContentLength == -1)
if (contentLength == -1)
{
ContentLength = long.MaxValue;
contentLength = long.MaxValue;
}
int bytesToRead = bufferSize;
if (ContentLength < bufferSize)
if (contentLength < bufferSize)
{
bytesToRead = (int)ContentLength;
bytesToRead = (int)contentLength;
}
var buffer = new byte[bufferSize];
......@@ -153,11 +149,11 @@ namespace Titanium.Web.Proxy.Extensions
await outStream.WriteAsync(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
if (totalBytesRead == ContentLength)
if (totalBytesRead == contentLength)
break;
bytesRead = 0;
var remainingBytes = (ContentLength - totalBytesRead);
var remainingBytes = (contentLength - totalBytesRead);
bytesToRead = remainingBytes > (long)bufferSize ? bufferSize : (int)remainingBytes;
}
}
......@@ -167,13 +163,14 @@ namespace Titanium.Web.Proxy.Extensions
}
}
/// <summary>
/// Copies the streams chunked
/// </summary>
/// <param name="inStreamReader"></param>
/// <param name="outStream"></param>
/// <returns></returns>
internal static async Task WriteResponseBodyChunked(this CustomBinaryReader inStreamReader, int bufferSize, Stream outStream)
/// <summary>
/// Copies the streams chunked
/// </summary>
/// <param name="inStreamReader"></param>
/// <param name="bufferSize"></param>
/// <param name="outStream"></param>
/// <returns></returns>
internal static async Task WriteResponseBodyChunked(this CustomBinaryReader inStreamReader, int bufferSize, Stream outStream)
{
while (true)
{
......
......@@ -37,37 +37,30 @@ namespace Titanium.Web.Proxy.Helpers
{
using (var readBuffer = new MemoryStream())
{
try
{
var lastChar = default(char);
var buffer = new byte[1];
while ((await this.stream.ReadAsync(buffer, 0, 1)) > 0)
{
//if new line
if (lastChar == '\r' && buffer[0] == '\n')
{
var result = readBuffer.ToArray();
return encoding.GetString(result.SubArray(0, result.Length - 1));
}
//end of stream
if (buffer[0] == '\0')
{
return encoding.GetString(readBuffer.ToArray());
}
await readBuffer.WriteAsync(buffer,0,1);
//store last char for new line comparison
lastChar = (char)buffer[0];
}
return encoding.GetString(readBuffer.ToArray());
}
catch (IOException)
{
throw;
}
var lastChar = default(char);
var buffer = new byte[1];
while ((await this.stream.ReadAsync(buffer, 0, 1)) > 0)
{
//if new line
if (lastChar == '\r' && buffer[0] == '\n')
{
var result = readBuffer.ToArray();
return encoding.GetString(result.SubArray(0, result.Length - 1));
}
//end of stream
if (buffer[0] == '\0')
{
return encoding.GetString(readBuffer.ToArray());
}
await readBuffer.WriteAsync(buffer,0,1);
//store last char for new line comparison
lastChar = (char)buffer[0];
}
return encoding.GetString(readBuffer.ToArray());
}
}
......
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;
}
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Extensions;
......@@ -234,13 +233,14 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Request Url
/// </summary>
public string Url { get { return RequestUri.OriginalString; } }
public string Url => RequestUri.OriginalString;
/// <summary>
/// <summary>
/// Encoding for this request
/// </summary>
internal Encoding Encoding { get { return this.GetEncoding(); } }
/// <summary>
internal Encoding Encoding => this.GetEncoding();
/// <summary>
/// Terminates the underlying Tcp Connection to client after current request
/// </summary>
internal bool CancelRequest { get; set; }
......
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Extensions;
......@@ -16,9 +15,9 @@ namespace Titanium.Web.Proxy.Http
public string ResponseStatusCode { 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
/// </summary>
internal string ContentEncoding
......@@ -205,7 +204,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Response network stream
/// </summary>
internal Stream ResponseStream { get; set; }
public Stream ResponseStream { get; set; }
/// <summary>
/// response body contenst as byte array
......
......@@ -3,7 +3,7 @@
/// <summary>
/// 200 Ok response
/// </summary>
public class OkResponse : Response
public sealed class OkResponse : Response
{
public OkResponse()
{
......
using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.Http.Responses
namespace Titanium.Web.Proxy.Http.Responses
{
/// <summary>
/// Redirect response
/// </summary>
public class RedirectResponse : Response
public sealed class RedirectResponse : Response
{
public RedirectResponse()
{
......
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
namespace Titanium.Web.Proxy.Models
{
/// <summary>
/// An abstract endpoint where the proxy listens
/// </summary>
public abstract class ProxyEndPoint
{
public ProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
{
this.IpAddress = IpAddress;
this.Port = Port;
this.EnableSsl = EnableSsl;
}
public IPAddress IpAddress { get; internal set; }
public int Port { get; internal set; }
public bool EnableSsl { get; internal set; }
internal TcpListener listener { get; set; }
}
/// <summary>
/// A proxy endpoint that the client is aware of
/// So client application know that it is communicating with a proxy server
/// </summary>
public class ExplicitProxyEndPoint : ProxyEndPoint
{
internal bool IsSystemHttpProxy { get; set; }
internal bool IsSystemHttpsProxy { get; set; }
public List<string> ExcludedHttpsHostNameRegex { get; set; }
public ExplicitProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
: base(IpAddress, Port, EnableSsl)
{
}
}
/// <summary>
/// A proxy end point client is not aware of
/// Usefull when requests are redirected to this proxy end point through port forwarding
/// </summary>
public class TransparentProxyEndPoint : ProxyEndPoint
{
//Name of the Certificate need to be sent (same as the hostname we want to proxy)
//This is valid only when UseServerNameIndication is set to false
public string GenericCertificateName { get; set; }
// public bool UseServerNameIndication { get; set; }
public TransparentProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
: base(IpAddress, Port, EnableSsl)
{
this.GenericCertificateName = "localhost";
}
}
}
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
namespace Titanium.Web.Proxy.Models
{
/// <summary>
/// An abstract endpoint where the proxy listens
/// </summary>
public abstract class ProxyEndPoint
{
public ProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
{
this.IpAddress = IpAddress;
this.Port = Port;
this.EnableSsl = EnableSsl;
}
public IPAddress IpAddress { get; internal set; }
public int Port { 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; }
}
/// <summary>
/// A proxy endpoint that the client is aware of
/// So client application know that it is communicating with a proxy server
/// </summary>
public class ExplicitProxyEndPoint : ProxyEndPoint
{
internal bool IsSystemHttpProxy { get; set; }
internal bool IsSystemHttpsProxy { get; set; }
public List<string> ExcludedHttpsHostNameRegex { get; set; }
public ExplicitProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
: base(IpAddress, Port, EnableSsl)
{
}
}
/// <summary>
/// A proxy end point client is not aware of
/// Usefull when requests are redirected to this proxy end point through port forwarding
/// </summary>
public class TransparentProxyEndPoint : ProxyEndPoint
{
//Name of the Certificate need to be sent (same as the hostname we want to proxy)
//This is valid only when UseServerNameIndication is set to false
public string GenericCertificateName { get; set; }
// public bool UseServerNameIndication { get; set; }
public TransparentProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
: base(IpAddress, Port, EnableSsl)
{
this.GenericCertificateName = "localhost";
}
}
}
namespace Titanium.Web.Proxy.Models
{
/// <summary>
/// An upstream proxy this proxy uses if any
/// </summary>
public class ExternalProxy
{
public string HostName { get; set; }
public int Port { get; set; }
}
}
using System;
using System.Net;
namespace Titanium.Web.Proxy.Models
{
/// <summary>
/// An upstream proxy this proxy uses if any
/// </summary>
public class ExternalProxy
{
private static readonly Lazy<NetworkCredential> DefaultCredentials = new Lazy<NetworkCredential>(() => CredentialCache.DefaultNetworkCredentials);
private string userName;
private string password;
public bool UseDefaultCredentials { get; set; }
public string UserName {
get { return UseDefaultCredentials ? DefaultCredentials.Value.UserName : userName; }
set
{
userName = value;
if (DefaultCredentials.Value.UserName != userName)
{
UseDefaultCredentials = false;
}
}
}
public string Password
{
get { return UseDefaultCredentials ? DefaultCredentials.Value.Password : password; }
set
{
password = value;
if (DefaultCredentials.Value.Password != password)
{
UseDefaultCredentials = false;
}
}
}
public string HostName { get; set; }
public int Port { get; set; }
}
}
This diff is collapsed.
......@@ -2,6 +2,7 @@
using System.IO;
using System.Net.Sockets;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network
{
......@@ -10,11 +11,20 @@ namespace Titanium.Web.Proxy.Network
/// </summary>
public class TcpConnection : IDisposable
{
internal ExternalProxy UpStreamHttpProxy { get; set; }
internal ExternalProxy UpStreamHttpsProxy { get; set; }
internal string HostName { get; set; }
internal int port { get; set; }
internal int Port { get; set; }
internal bool IsHttps { get; set; }
/// <summary>
/// Http version
/// </summary>
internal Version Version { get; set; }
internal TcpClient TcpClient { get; set; }
/// <summary>
......@@ -27,6 +37,16 @@ namespace Titanium.Web.Proxy.Network
/// </summary>
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()
{
Stream.Close();
......
......@@ -7,6 +7,7 @@ using System.Net.Security;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using System.Security.Authentication;
using System.Linq;
namespace Titanium.Web.Proxy.Network
{
......@@ -34,7 +35,7 @@ namespace Titanium.Web.Proxy.Network
/// <param name="clientStream"></param>
/// <returns></returns>
internal async Task<TcpConnection> CreateClient(int bufferSize, int connectionTimeOutSeconds,
string remoteHostName, int remotePort, Version httpVersion,
string remoteHostName, int remotePort, Version httpVersion,
bool isHttps, SslProtocols supportedSslProtocols,
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy,
......@@ -48,16 +49,22 @@ namespace Titanium.Web.Proxy.Network
SslStream sslStream = null;
//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);
stream = client.GetStream();
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(string.Format("Host: {0}:{1}", remoteHostName, remotePort));
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
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.FlushAsync();
writer.Close();
......@@ -67,7 +74,8 @@ namespace Titanium.Web.Proxy.Network
{
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");
}
......@@ -92,17 +100,14 @@ namespace Titanium.Web.Proxy.Network
}
catch
{
if (sslStream != null)
{
sslStream.Dispose();
}
sslStream?.Dispose();
throw;
}
}
else
{
if (externalHttpProxy != null)
if (externalHttpProxy != null && externalHttpProxy.HostName != remoteHostName)
{
client = new TcpClient(externalHttpProxy.HostName, externalHttpProxy.Port);
stream = client.GetStream();
......@@ -119,15 +124,18 @@ namespace Titanium.Web.Proxy.Network
stream.ReadTimeout = connectionTimeOutSeconds * 1000;
stream.WriteTimeout = connectionTimeOutSeconds * 1000;
return new TcpConnection()
{
UpStreamHttpProxy = externalHttpProxy,
UpStreamHttpsProxy = externalHttpsProxy,
HostName = remoteHostName,
port = remotePort,
Port = remotePort,
IsHttps = isHttps,
TcpClient = client,
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;
}
}
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<Prefer32Bit>false</Prefer32Bit>
<DocumentationFile>bin\Debug\Titanium.Web.Proxy.XML</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
<PlatformTarget>AnyCPU</PlatformTarget>
......@@ -32,6 +33,7 @@
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<DefineConstants>NET45</DefineConstants>
<Prefer32Bit>false</Prefer32Bit>
<DocumentationFile>bin\Release\Titanium.Web.Proxy.XML</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="Ionic.Zip, Version=1.9.8.0, Culture=neutral, PublicKeyToken=6583c7c814667745, processorArchitecture=MSIL">
......@@ -64,9 +66,14 @@
<Compile Include="EventArguments\CertificateSelectionEventArgs.cs" />
<Compile Include="EventArguments\CertificateValidationEventArgs.cs" />
<Compile Include="Extensions\ByteArrayExtensions.cs" />
<Compile Include="Helpers\Network.cs" />
<Compile Include="Network\CachedCertificate.cs" />
<Compile Include="Network\CertificateMaker.cs" />
<Compile Include="Network\ProxyClient.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\HttpWebRequestExtensions.cs" />
<Compile Include="Network\CertificateManager.cs" />
......@@ -82,6 +89,7 @@
<Compile Include="Models\HttpHeader.cs" />
<Compile Include="Http\HttpWebClient.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ProxyAuthorizationHandler.cs" />
<Compile Include="RequestHandler.cs" />
<Compile Include="ResponseHandler.cs" />
<Compile Include="Helpers\CustomBinaryReader.cs" />
......@@ -92,16 +100,23 @@
<Compile Include="Http\Responses\OkResponse.cs" />
<Compile Include="Http\Responses\RedirectResponse.cs" />
<Compile Include="Shared\ProxyConstants.cs" />
<Compile Include="Tcp\TcpRow.cs" />
<Compile Include="Tcp\TcpTable.cs" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
<COMReference Include="CERTENROLLLib">
<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>
<None Include="makecert.exe">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
......
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