Skip to content
This repository was archived by the owner on Jul 9, 2023. It is now read-only.
Merged

Beta #230

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 36 additions & 23 deletions Examples/Titanium.Web.Proxy.Examples.Basic/ProxyTestController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
Expand All @@ -17,6 +18,12 @@ public class ProxyTestController
public ProxyTestController()
{
proxyServer = new ProxyServer();

//generate root certificate without storing it in file system
//proxyServer.CertificateEngine = Network.CertificateEngine.BouncyCastle;
//proxyServer.CertificateManager.CreateTrustedRootCertificate(false);
//proxyServer.CertificateManager.TrustRootCertificate();

proxyServer.ExceptionFunc = exception => Console.WriteLine(exception.Message);
proxyServer.TrustRootCertificate = true;

Expand All @@ -40,7 +47,7 @@ public void StartProxy()
//Exclude Https addresses you don't want to proxy
//Useful for clients that use certificate pinning
//for example google.com and dropbox.com
// ExcludedHttpsHostNameRegex = new List<string>() { "google.com", "dropbox.com" }
ExcludedHttpsHostNameRegex = new List<string>() { "dropbox.com" }

//Include Https addresses you want to proxy (others will be excluded)
//for example github.com
Expand Down Expand Up @@ -92,18 +99,22 @@ public void Stop()
proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection;

proxyServer.Stop();

//remove the generated certificates
//proxyServer.CertificateManager.RemoveTrustedRootCertificates();
}

//intecept & cancel redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine("Active Client Connections:" + ((ProxyServer) sender).ClientConnectionCount);
Console.WriteLine(e.WebSession.Request.Url);

//read request headers
var requestHeaders = e.WebSession.Request.RequestHeaders;

var method = e.WebSession.Request.Method.ToUpper();
if ((method == "POST" || method == "PUT" || method == "PATCH"))
if (method == "POST" || method == "PUT" || method == "PATCH")
{
//Get/Set request body bytes
byte[] bodyBytes = await e.GetRequestBody();
Expand All @@ -116,30 +127,32 @@ public async Task OnRequest(object sender, SessionEventArgs e)
requestBodyHistory[e.Id] = bodyString;
}

//To cancel a request with a custom HTML content
//Filter URL
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
{
await e.Ok("<!DOCTYPE html>" +
"<html><body><h1>" +
"Website Blocked" +
"</h1>" +
"<p>Blocked by titanium web proxy.</p>" +
"</body>" +
"</html>");
}

//Redirect example
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
{
await e.Redirect("https://www.paypal.com");
}
////To cancel a request with a custom HTML content
////Filter URL
//if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
//{
// await e.Ok("<!DOCTYPE html>" +
// "<html><body><h1>" +
// "Website Blocked" +
// "</h1>" +
// "<p>Blocked by titanium web proxy.</p>" +
// "</body>" +
// "</html>");
//}

////Redirect example
//if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
//{
// await e.Redirect("https://www.paypal.com");
//}
}

//Modify response
public async Task OnResponse(object sender, SessionEventArgs e)
{
if(requestBodyHistory.ContainsKey(e.Id))
Console.WriteLine("Active Server Connections:" + (sender as ProxyServer).ServerConnectionCount);

if (requestBodyHistory.ContainsKey(e.Id))
{
//access request body by looking up the shared dictionary using requestId
var requestBody = requestBodyHistory[e.Id];
Expand All @@ -149,14 +162,14 @@ public async Task OnResponse(object sender, SessionEventArgs e)
var responseHeaders = e.WebSession.Response.ResponseHeaders;

// print out process id of current session
Console.WriteLine($"PID: {e.WebSession.ProcessId.Value}");
//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")
{
if (e.WebSession.Response.ResponseStatusCode == "200")
{
if (e.WebSession.Response.ContentType!=null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html"))
if (e.WebSession.Response.ContentType != null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html"))
{
byte[] bodyBytes = await e.GetResponseBody();
await e.SetResponseBody(bodyBytes);
Expand Down
4 changes: 2 additions & 2 deletions Titanium.Web.Proxy/CertificateHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public partial class ProxyServer
/// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param>
/// <returns></returns>
private bool ValidateServerCertificate(
internal bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
Expand Down Expand Up @@ -65,7 +65,7 @@ private bool ValidateServerCertificate(
/// <param name="remoteCertificate"></param>
/// <param name="acceptableIssuers"></param>
/// <returns></returns>
private X509Certificate SelectClientCertificate(
internal X509Certificate SelectClientCertificate(
object sender,
string targetHost,
X509CertificateCollection localCertificates,
Expand Down
3 changes: 2 additions & 1 deletion Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ private async Task ReadRequestBody()
{
//GET request don't have a request body to read
var method = WebSession.Request.Method.ToUpper();
if ((method != "POST" && method != "PUT" && method != "PATCH"))
if (method != "POST" && method != "PUT" && method != "PATCH")
{
throw new BodyNotFoundException("Request don't have a body. " +
"Please verify that this request is a Http POST/PUT/PATCH and request " +
Expand Down Expand Up @@ -411,6 +411,7 @@ public async Task Ok(byte[] result, Dictionary<string, HttpHeader> headers)
{
response.ResponseHeaders = headers;
}

response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseBody = result;

Expand Down
21 changes: 21 additions & 0 deletions Titanium.Web.Proxy/Extensions/TcpExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Net.Sockets;
using Titanium.Web.Proxy.Helpers;

namespace Titanium.Web.Proxy.Extensions
{
Expand Down Expand Up @@ -32,5 +33,25 @@ internal static bool IsConnected(this Socket client)
client.Blocking = blockingState;
}
}

/// <summary>
/// Gets the local port from a native TCP row object.
/// </summary>
/// <param name="tcpRow">The TCP row.</param>
/// <returns>The local port</returns>
internal static int GetLocalPort(this NativeMethods.TcpRow tcpRow)
{
return (tcpRow.localPort1 << 8) + tcpRow.localPort2 + (tcpRow.localPort3 << 24) + (tcpRow.localPort4 << 16);
}

/// <summary>
/// Gets the remote port from a native TCP row object.
/// </summary>
/// <param name="tcpRow">The TCP row.</param>
/// <returns>The remote port</returns>
internal static int GetRemotePort(this NativeMethods.TcpRow tcpRow)
{
return (tcpRow.remotePort1 << 8) + tcpRow.remotePort2 + (tcpRow.remotePort3 << 24) + (tcpRow.remotePort4 << 16);
}
}
}
24 changes: 7 additions & 17 deletions Titanium.Web.Proxy/Helpers/CustomBinaryReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,14 @@ internal class CustomBinaryReader : IDisposable
{
private readonly CustomBufferedStream stream;
private readonly int bufferSize;
private readonly byte[] staticBuffer;
private readonly Encoding encoding;

[ThreadStatic]
private static byte[] staticBufferField;

private byte[] staticBuffer
{
get
{
if (staticBufferField == null || staticBufferField.Length != bufferSize)
{
staticBufferField = new byte[bufferSize];
}

return staticBufferField;
}
}

internal CustomBinaryReader(CustomBufferedStream stream, int bufferSize)
{
this.stream = stream;
staticBuffer = new byte[bufferSize];

this.bufferSize = bufferSize;

//default to UTF-8
Expand Down Expand Up @@ -122,10 +109,13 @@ internal async Task<byte[]> ReadBytesAsync(long totalBytesToRead)
{
int bytesToRead = bufferSize;

var buffer = staticBuffer;
if (totalBytesToRead < bufferSize)
{
bytesToRead = (int) totalBytesToRead;
buffer = new byte[bytesToRead];
}

var buffer = staticBuffer;
int bytesRead;
var totalBytesRead = 0;

Expand Down
2 changes: 2 additions & 0 deletions Titanium.Web.Proxy/Helpers/CustomBufferedStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ public override async Task CopyToAsync(Stream destination, int bufferSize, Cance
if (bufferLength > 0)
{
await destination.WriteAsync(streamBuffer, bufferPos, bufferLength, cancellationToken);
bufferLength = 0;
}

await baseStream.CopyToAsync(destination, bufferSize, cancellationToken);
Expand Down Expand Up @@ -307,6 +308,7 @@ public byte ReadByteFromBuffer()
/// <returns>
/// A task that represents the asynchronous write operation.
/// </returns>
[DebuggerStepThrough]
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return baseStream.WriteAsync(buffer, offset, count, cancellationToken);
Expand Down
3 changes: 1 addition & 2 deletions Titanium.Web.Proxy/Helpers/Network.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ internal class NetworkHelper
{
private static int FindProcessIdFromLocalPort(int port, IpVersion ipVersion)
{
var tcpRow = TcpHelper.GetExtendedTcpTable(ipVersion).FirstOrDefault(
row => row.LocalEndPoint.Port == port);
var tcpRow = TcpHelper.GetTcpRowByLocalPort(ipVersion, port);

return tcpRow?.ProcessId ?? 0;
}
Expand Down
83 changes: 62 additions & 21 deletions Titanium.Web.Proxy/Helpers/Tcp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,21 +93,21 @@ internal static TcpTable GetExtendedTcpTable(IpVersion ipVersion)

var ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6;

if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, (int) NativeMethods.TcpTableType.OwnerPidAll, 0) != 0)
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)
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));
NativeMethods.TcpTable table = (NativeMethods.TcpTable)Marshal.PtrToStructure(tcpTable, typeof(NativeMethods.TcpTable));

IntPtr rowPtr = (IntPtr) ((long) tcpTable + Marshal.SizeOf(table.length));
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)));
tcpRows.Add(new TcpRow((NativeMethods.TcpRow)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow))));
rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow)));
}
}
}
Expand All @@ -123,30 +123,71 @@ internal static TcpTable GetExtendedTcpTable(IpVersion ipVersion)
return new TcpTable(tcpRows);
}

/// <summary>
/// Gets the TCP row by local port number.
/// </summary>
/// <returns><see cref="TcpRow"/>.</returns>
internal static TcpRow GetTcpRowByLocalPort(IpVersion ipVersion, int localPort)
{
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)
{
var tcpRow = (NativeMethods.TcpRow)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow));
if (tcpRow.GetLocalPort() == localPort)
{
return new TcpRow(tcpRow);
}

rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow)));
}
}
}
finally
{
if (tcpTable != IntPtr.Zero)
{
Marshal.FreeHGlobal(tcpTable);
}
}
}

return null;
}

/// <summary>
/// relays the input clientStream to the server at the specified host name and port with the given httpCmd and headers as prefix
/// Usefull for websocket requests
/// </summary>
/// <param name="bufferSize"></param>
/// <param name="connectionTimeOutSeconds"></param>
/// <param name="server"></param>
/// <param name="remoteHostName"></param>
/// <param name="remotePort"></param>
/// <param name="httpCmd"></param>
/// <param name="httpVersion"></param>
/// <param name="requestHeaders"></param>
/// <param name="isHttps"></param>
/// <param name="remotePort"></param>
/// <param name="supportedProtocols"></param>
/// <param name="remoteCertificateValidationCallback"></param>
/// <param name="localCertificateSelectionCallback"></param>
/// <param name="clientStream"></param>
/// <param name="tcpConnectionFactory"></param>
/// <param name="upStreamEndPoint"></param>
/// <returns></returns>
internal static async Task SendRaw(int bufferSize, int connectionTimeOutSeconds,
string remoteHostName, int remotePort, string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders,
bool isHttps, SslProtocols supportedProtocols,
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
Stream clientStream, TcpConnectionFactory tcpConnectionFactory, IPEndPoint upStreamEndPoint)
internal static async Task SendRaw(ProxyServer server,
string remoteHostName, int remotePort,
string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders,
bool isHttps,
Stream clientStream, TcpConnectionFactory tcpConnectionFactory)
{
//prepare the prefix content
StringBuilder sb = null;
Expand All @@ -172,11 +213,10 @@ internal static async Task SendRaw(int bufferSize, int connectionTimeOutSeconds,
sb.Append(ProxyConstants.NewLine);
}

var tcpConnection = await tcpConnectionFactory.CreateClient(bufferSize, connectionTimeOutSeconds,
var tcpConnection = await tcpConnectionFactory.CreateClient(server,
remoteHostName, remotePort,
httpVersion, isHttps,
supportedProtocols, remoteCertificateValidationCallback, localCertificateSelectionCallback,
null, null, clientStream, upStreamEndPoint);
null, null, clientStream);

try
{
Expand All @@ -192,6 +232,7 @@ internal static async Task SendRaw(int bufferSize, int connectionTimeOutSeconds,
finally
{
tcpConnection.Dispose();
server.ServerConnectionCount--;
}
}
}
Expand Down
Loading