Unverified Commit 4a7ccfac authored by honfika's avatar honfika Committed by GitHub

Merge pull request #625 from justcoding121/beta

Stable
parents 07377916 53ba585d
## Titanium Web Proxy
### Note: This Project is no longer maintained. Any pull requests for fixes are welcome.
A lightweight HTTP(S) proxy server written in C#.
<a href="https://ci.appveyor.com/project/justcoding121/titanium-web-proxy">![Build Status](https://ci.appveyor.com/api/projects/status/p5vvtbpx9yp250ol?svg=true)</a> [![Join the chat at https://gitter.im/Titanium-Web-Proxy/Lobby](https://badges.gitter.im/Titanium-Web-Proxy/Lobby.svg)](https://gitter.im/Titanium-Web-Proxy/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
......@@ -32,7 +30,7 @@ For stable releases on [stable branch](https://github.com/justcoding121/Titanium
Supports
* .Net Standard 2.0 or above
* .Net Framework 4.5 or above
* .Net Framework 4.6.1 or above
### Development environment
......@@ -57,11 +55,11 @@ Setup HTTP proxy:
```csharp
var proxyServer = new ProxyServer();
//locally trust root certificate used by this proxy
// locally trust root certificate used by this proxy
proxyServer.CertificateManager.TrustRootCertificate = true;
//optionally set the Certificate Engine
//Under Mono only BouncyCastle will be supported
// optionally set the Certificate Engine
// Under Mono only BouncyCastle will be supported
//proxyServer.CertificateManager.CertificateEngine = Network.CertificateEngine.BouncyCastle;
proxyServer.BeforeRequest += OnRequest;
......@@ -72,28 +70,28 @@ proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{
//Use self-issued generic certificate on all https requests
//Optimizes performance by not creating a certificate for each https-enabled domain
//Useful when certificate trust is not required by proxy clients
//GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password")
// Use self-issued generic certificate on all https requests
// Optimizes performance by not creating a certificate for each https-enabled domain
// Useful when certificate trust is not required by proxy clients
//GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password")
};
//Fired when a CONNECT request is received
// Fired when a CONNECT request is received
explicitEndPoint.BeforeTunnelConnect += OnBeforeTunnelConnect;
//An explicit endpoint is where the client knows about the existence of a proxy
//So client sends request in a proxy friendly manner
// An explicit endpoint is where the client knows about the existence of a proxy
// So client sends request in a proxy friendly manner
proxyServer.AddEndPoint(explicitEndPoint);
proxyServer.Start();
//Transparent endpoint is useful for reverse proxy (client is not aware of the existence of proxy)
//A transparent endpoint usually requires a network router port forwarding HTTP(S) packets or DNS
//to send data to this endPoint
// Transparent endpoint is useful for reverse proxy (client is not aware of the existence of proxy)
// A transparent endpoint usually requires a network router port forwarding HTTP(S) packets or DNS
// to send data to this endPoint
var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true)
{
//Generic Certificate hostname to use
//when SNI is disabled by client
GenericCertificateName = "google.com"
// Generic Certificate hostname to use
// when SNI is disabled by client
GenericCertificateName = "google.com"
};
proxyServer.AddEndPoint(transparentEndPoint);
......@@ -105,14 +103,14 @@ foreach (var endPoint in proxyServer.ProxyEndPoints)
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
//Only explicit proxies can be set as system proxy!
// Only explicit proxies can be set as system proxy!
proxyServer.SetAsSystemHttpProxy(explicitEndPoint);
proxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
//wait here (You can use something else as a wait function, I am using this as a demo)
// wait here (You can use something else as a wait function, I am using this as a demo)
Console.Read();
//Unsubscribe & Quit
// Unsubscribe & Quit
explicitEndPoint.BeforeTunnelConnect -= OnBeforeTunnelConnect;
proxyServer.BeforeRequest -= OnRequest;
proxyServer.BeforeResponse -= OnResponse;
......@@ -120,11 +118,11 @@ proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection;
proxyServer.Stop();
```
Sample request and response event handlers
```csharp
```csharp
private async Task OnBeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
{
......@@ -132,9 +130,9 @@ private async Task OnBeforeTunnelConnectRequest(object sender, TunnelConnectSess
if (hostname.Contains("dropbox.com"))
{
//Exclude Https addresses you don't want to proxy
//Useful for clients that use certificate pinning
//for example dropbox.com
// Exclude Https addresses you don't want to proxy
// Useful for clients that use certificate pinning
// for example dropbox.com
e.DecryptSsl = false;
}
}
......@@ -143,88 +141,88 @@ public async Task OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine(e.HttpClient.Request.Url);
////read request headers
// read request headers
var requestHeaders = e.HttpClient.Request.RequestHeaders;
var method = e.HttpClient.Request.Method.ToUpper();
if ((method == "POST" || method == "PUT" || method == "PATCH"))
{
//Get/Set request body bytes
byte[] bodyBytes = await e.GetRequestBody();
await e.SetRequestBody(bodyBytes);
//Get/Set request body as string
string bodyString = await e.GetRequestBodyAsString();
await e.SetRequestBodyString(bodyString);
//store request
//so that you can find it from response handler
e.UserData = e.HttpClient.Request;
// Get/Set request body bytes
byte[] bodyBytes = await e.GetRequestBody();
await e.SetRequestBody(bodyBytes);
// Get/Set request body as string
string bodyString = await e.GetRequestBodyAsString();
await e.SetRequestBodyString(bodyString);
// store request
// so that you can find it from response handler
e.UserData = e.HttpClient.Request;
}
//To cancel a request with a custom HTML content
//Filter URL
// To cancel a request with a custom HTML content
// Filter URL
if (e.HttpClient.Request.RequestUri.AbsoluteUri.Contains("google.com"))
{
e.Ok("<!DOCTYPE html>" +
"<html><body><h1>" +
"Website Blocked" +
"</h1>" +
"<p>Blocked by titanium web proxy.</p>" +
"</body>" +
"</html>");
e.Ok("<!DOCTYPE html>" +
"<html><body><h1>" +
"Website Blocked" +
"</h1>" +
"<p>Blocked by titanium web proxy.</p>" +
"</body>" +
"</html>");
}
//Redirect example
// Redirect example
if (e.HttpClient.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
{
e.Redirect("https://www.paypal.com");
e.Redirect("https://www.paypal.com");
}
}
//Modify response
// Modify response
public async Task OnResponse(object sender, SessionEventArgs e)
{
//read response headers
// read response headers
var responseHeaders = e.HttpClient.Response.ResponseHeaders;
//if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
if (e.HttpClient.Request.Method == "GET" || e.HttpClient.Request.Method == "POST")
{
if (e.HttpClient.Response.ResponseStatusCode == "200")
{
if (e.HttpClient.Response.ContentType!=null && e.HttpClient.Response.ContentType.Trim().ToLower().Contains("text/html"))
{
byte[] bodyBytes = await e.GetResponseBody();
await e.SetResponseBody(bodyBytes);
string body = await e.GetResponseBodyAsString();
await e.SetResponseBodyString(body);
}
}
if (e.HttpClient.Response.ResponseStatusCode == "200")
{
if (e.HttpClient.Response.ContentType!=null && e.HttpClient.Response.ContentType.Trim().ToLower().Contains("text/html"))
{
byte[] bodyBytes = await e.GetResponseBody();
await e.SetResponseBody(bodyBytes);
string body = await e.GetResponseBodyAsString();
await e.SetResponseBodyString(body);
}
}
}
if(e.UserData!=null)
if (e.UserData!=null)
{
//access request from UserData property where we stored it in RequestHandler
var request = (Request)e.UserData;
// access request from UserData property where we stored it in RequestHandler
var request = (Request)e.UserData;
}
}
/// Allows overriding default certificate validation logic
// Allows overriding default certificate validation logic
public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{
//set IsValid to true/false based on Certificate Errors
// set IsValid to true/false based on Certificate Errors
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
e.IsValid = true;
e.IsValid = true;
return Task.FromResult(0);
}
/// Allows overriding default client certificate selection logic during mutual authentication
// Allows overriding default client certificate selection logic during mutual authentication
public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e)
{
//set e.clientCertificate to override
// set e.clientCertificate to override
return Task.FromResult(0);
}
```
......
......@@ -119,7 +119,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
private async Task onBeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
{
string hostname = e.HttpClient.Request.RequestUri.Host;
//await writeToConsole("Tunnel to: " + hostname);
await writeToConsole("Tunnel to: " + hostname);
if (hostname.Contains("dropbox.com"))
{
......@@ -138,8 +138,8 @@ namespace Titanium.Web.Proxy.Examples.Basic
// intecept & cancel redirect or update requests
private async Task onRequest(object sender, SessionEventArgs e)
{
//await writeToConsole("Active Client Connections:" + ((ProxyServer)sender).ClientConnectionCount);
//await writeToConsole(e.HttpClient.Request.Url);
await writeToConsole("Active Client Connections:" + ((ProxyServer)sender).ClientConnectionCount);
await writeToConsole(e.HttpClient.Request.Url);
// store it in the UserData property
// It can be a simple integer, Guid, or any type
......@@ -189,11 +189,11 @@ namespace Titanium.Web.Proxy.Examples.Basic
private async Task onResponse(object sender, SessionEventArgs e)
{
//await writeToConsole("Active Server Connections:" + ((ProxyServer)sender).ServerConnectionCount);
await writeToConsole("Active Server Connections:" + ((ProxyServer)sender).ServerConnectionCount);
string ext = System.IO.Path.GetExtension(e.HttpClient.Request.RequestUri.AbsolutePath);
//access user data set in request to do something with it
// access user data set in request to do something with it
//var userData = e.HttpClient.UserData as CustomUserData;
//if (ext == ".gif" || ext == ".png" || ext == ".jpg")
......
......@@ -615,36 +615,36 @@ namespace Titanium.Web.Proxy.EventArguments
/// <param name="closeServerConnection">Close the server connection used by request if any?</param>
public void Respond(Response response, bool closeServerConnection = false)
{
//request already send/ready to be sent.
// request already send/ready to be sent.
if (HttpClient.Request.Locked)
{
//response already received from server and ready to be sent to client.
// response already received from server and ready to be sent to client.
if (HttpClient.Response.Locked)
{
throw new Exception("You cannot call this function after response is sent to the client.");
}
//cleanup original response.
// cleanup original response.
if (closeServerConnection)
{
//no need to cleanup original connection.
//it will be closed any way.
// no need to cleanup original connection.
// it will be closed any way.
TerminateServerConnection();
}
response.SetOriginalHeaders(HttpClient.Response);
//response already received from server but not yet ready to sent to client.
// response already received from server but not yet ready to sent to client.
HttpClient.Response = response;
HttpClient.Response.Locked = true;
}
//request not yet sent/not yet ready to be sent.
// request not yet sent/not yet ready to be sent.
else
{
HttpClient.Request.Locked = true;
HttpClient.Request.CancelRequest = true;
//set new response.
// set new response.
HttpClient.Response = response;
HttpClient.Response.Locked = true;
}
......
......@@ -134,6 +134,7 @@ namespace Titanium.Web.Proxy
if (decryptSsl && isClientHello)
{
clientConnection.SslProtocol = clientHelloInfo.SslProtocol;
connectRequest.RequestUri = new Uri("https://" + httpUrl);
bool http2Supported = false;
......@@ -151,7 +152,8 @@ namespace Titanium.Web.Proxy
http2Supported = connection.NegotiatedApplicationProtocol ==
SslApplicationProtocol.Http2;
//release connection back to pool instead of closing when connection pool is enabled.
// release connection back to pool instead of closing when connection pool is enabled.
await tcpConnectionFactory.Release(connection, true);
}
catch (Exception)
......@@ -165,15 +167,15 @@ namespace Titanium.Web.Proxy
IPAddress[] ipAddresses = null;
try
{
//make sure the host can be resolved before creating the prefetch task
// make sure the host can be resolved before creating the prefetch task
ipAddresses = await Dns.GetHostAddressesAsync(connectArgs.HttpClient.Request.RequestUri.Host);
}
catch (SocketException) { }
if (ipAddresses != null && ipAddresses.Length > 0)
{
//don't pass cancellation token here
//it could cause floating server connections when client exits
// don't pass cancellation token here
// it could cause floating server connections when client exits
prefetchConnectionTask = tcpConnectionFactory.GetServerConnection(this, connectArgs,
isConnect: true, applicationProtocols: null, noCache: false,
cancellationToken: CancellationToken.None);
......
......@@ -50,11 +50,11 @@ namespace Titanium.Web.Proxy.Extensions
socket.Blocking = false;
socket.Send(tmp, 0, 0);
//Connected.
// Connected.
}
catch
{
//Should we let 10035 == WSAEWOULDBLOCK as valid connection?
// Should we let 10035 == WSAEWOULDBLOCK as valid connection?
return false;
}
finally
......
using System;
namespace Titanium.Web.Proxy.Extensions
{
internal static class UriExtensions
{
internal static string GetOriginalPathAndQuery(this Uri uri)
{
string leftPart = uri.GetLeftPart(UriPartial.Authority);
if (uri.OriginalString.StartsWith(leftPart))
return uri.OriginalString.Substring(leftPart.Length);
return uri.IsWellFormedOriginalString() ? uri.PathAndQuery : uri.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
}
}
}
......@@ -41,7 +41,7 @@ namespace Titanium.Web.Proxy.Helpers
return true;
}
//if hostname matches local host name
// if hostname matches local host name
if (hostName.Equals(localhostName, StringComparison.OrdinalIgnoreCase))
{
return true;
......
......@@ -42,7 +42,7 @@ namespace Titanium.Web.Proxy.Helpers
public static bool IsMac => isRunningOnMac;
//https://github.com/qmatteoq/DesktopBridgeHelpers/blob/master/DesktopBridge.Helpers/Helpers.cs
// https://github.com/qmatteoq/DesktopBridgeHelpers/blob/master/DesktopBridge.Helpers/Helpers.cs
private class UwpHelper
{
const long APPMODEL_ERROR_NO_PACKAGE = 15700L;
......
......@@ -104,8 +104,7 @@ namespace Titanium.Web.Proxy.Http
}
else
{
var uri = Request.RequestUri;
url = uri.IsWellFormedOriginalString() ? uri.PathAndQuery : uri.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
url = Request.RequestUri.GetOriginalPathAndQuery();
}
// prepare the request & headers
......
......@@ -105,8 +105,8 @@ namespace Titanium.Web.Proxy.Network.Certificate
// KeyLength
const int keyLength = 2048;
var graceTime = DateTime.Now.AddDays(graceDays);
var now = DateTime.Now;
var graceTime = now.AddDays(graceDays);
var certificate = makeCertificate(isRoot, sSubjectCN, fullSubject, keyLength, hashAlgo, graceTime,
now.AddDays(validDays), isRoot ? null : signingCert);
return certificate;
......
......@@ -483,7 +483,7 @@ namespace Titanium.Web.Proxy.Network
var cancellationToken = clearCertificatesTokenSource.Token;
while (!cancellationToken.IsCancellationRequested)
{
var cutOff = DateTime.Now.AddMinutes(-1 * CertificateCacheTimeOutMinutes);
var cutOff = DateTime.Now.AddMinutes(-CertificateCacheTimeOutMinutes);
var outdated = cachedCertificates.Where(x => x.Value.LastAccess < cutOff).ToList();
......
......@@ -37,10 +37,10 @@ namespace Titanium.Web.Proxy.Network
{
try
{
//setup connection
// setup connection
currentConnection = currentConnection as TcpServerConnection ??
await generator();
//try
// try
@continue = await action(currentConnection);
}
......@@ -65,12 +65,12 @@ namespace Titanium.Web.Proxy.Network
return new RetryResult(currentConnection, exception, @continue);
}
//before retry clear connection
// before retry clear connection
private async Task disposeConnection()
{
if (currentConnection != null)
{
//close connection on error
// close connection on error
await tcpConnectionFactory.Release(currentConnection, true);
currentConnection = null;
}
......
......@@ -5,6 +5,7 @@ using System.Net;
using System.Net.Security;
#endif
using System.Net.Sockets;
using System.Security.Authentication;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
......@@ -32,6 +33,8 @@ namespace Titanium.Web.Proxy.Network.Tcp
public EndPoint RemoteEndPoint => tcpClient.Client.RemoteEndPoint;
internal SslProtocols SslProtocol { get; set; }
internal SslApplicationProtocol NegotiatedApplicationProtocol { get; set; }
private readonly TcpClient tcpClient;
......@@ -79,9 +82,9 @@ namespace Titanium.Web.Proxy.Network.Tcp
{
Task.Run(async () =>
{
//delay calling tcp connection close()
//so that client have enough time to call close first.
//This way we can push tcp Time_Wait to client side when possible.
// delay calling tcp connection close()
// so that client have enough time to call close first.
// This way we can push tcp Time_Wait to client side when possible.
await Task.Delay(1000);
proxyServer.UpdateClientConnectionCount(false);
tcpClient.CloseSocket();
......
......@@ -90,9 +90,9 @@ namespace Titanium.Web.Proxy.Network.Tcp
{
Task.Run(async () =>
{
//delay calling tcp connection close()
//so that server have enough time to call close first.
//This way we can push tcp Time_Wait to server side when possible.
// delay calling tcp connection close()
// so that server have enough time to call close first.
// This way we can push tcp Time_Wait to server side when possible.
await Task.Delay(1000);
proxyServer.UpdateServerConnectionCount(false);
Stream?.Dispose();
......
......@@ -210,7 +210,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
// int cbBuffer;
// int BufferType;
// pvBuffer;
//What we need to do here is to grab a hold of the pvBuffer allocate by the individual
// What we need to do here is to grab a hold of the pvBuffer allocate by the individual
// SecBuffer and release it...
int currentOffset = index * Marshal.SizeOf(typeof(Buffer));
var secBufferpvBuffer = Marshal.ReadIntPtr(pBuffers,
......
......@@ -122,7 +122,9 @@ namespace Titanium.Web.Proxy
/// </summary>
private SystemProxyManager systemProxySettingsManager { get; }
//Number of exception retries when connection pool is enabled.
/// <summary>
/// Number of exception retries when connection pool is enabled.
/// </summary>
private int retries => EnableConnectionPool ? MaxCachedConnections : 0;
/// <summary>
......@@ -266,7 +268,7 @@ namespace Titanium.Web.Proxy
/// Local adapter/NIC endpoint where proxy makes request via.
/// Defaults via any IP addresses of this machine.
/// </summary>
public IPEndPoint UpStreamEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0);
public IPEndPoint UpStreamEndPoint { get; set; }
/// <summary>
/// A list of IpAddress and port this proxy is listening to.
......@@ -660,7 +662,7 @@ namespace Titanium.Web.Proxy
{
endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
//linux/macOS has a bug with socket reuse in .net core.
// linux/macOS has a bug with socket reuse in .net core.
if (ReuseSocket && RunTime.IsWindows)
{
endPoint.Listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
......@@ -871,13 +873,13 @@ namespace Titanium.Web.Proxy
/// <returns></returns>
internal async Task InvokeConnectionCreateEvent(TcpClient client, bool isClientConnection)
{
//client connection created
// client connection created
if (isClientConnection && OnClientConnectionCreate != null)
{
await OnClientConnectionCreate.InvokeAsync(this, client, ExceptionFunc);
}
//server connection created
// server connection created
if (!isClientConnection && OnServerConnectionCreate != null)
{
await OnServerConnectionCreate.InvokeAsync(this, client, ExceptionFunc);
......
......@@ -163,7 +163,7 @@ namespace Titanium.Web.Proxy
await args.GetRequestBody(cancellationToken);
}
//we need this to syphon out data from connection if API user changes them.
// we need this to syphon out data from connection if API user changes them.
request.SetOriginalHeaders();
args.TimeLine["Request Received"] = DateTime.Now;
......@@ -191,7 +191,7 @@ namespace Titanium.Web.Proxy
continue;
}
//If prefetch task is available.
// If prefetch task is available.
if (connection == null && prefetchTask != null)
{
try
......@@ -226,11 +226,11 @@ namespace Titanium.Web.Proxy
clientConnection.NegotiatedApplicationProtocol,
cancellationToken, cancellationTokenSource);
//update connection to latest used
// update connection to latest used
connection = result.LatestConnection;
closeServerConnection = !result.Continue;
//throw if exception happened
// throw if exception happened
if (!result.IsSuccess)
{
throw result.Exception;
......@@ -241,7 +241,7 @@ namespace Titanium.Web.Proxy
return;
}
//user requested
// user requested
if (args.HttpClient.CloseServerConnection)
{
closeServerConnection = true;
......@@ -304,13 +304,13 @@ namespace Titanium.Web.Proxy
TcpServerConnection serverConnection, SslApplicationProtocol sslApplicationProtocol,
CancellationToken cancellationToken, CancellationTokenSource cancellationTokenSource)
{
//a connection generator task with captured parameters via closure.
// a connection generator task with captured parameters via closure.
Func<Task<TcpServerConnection>> generator = () =>
tcpConnectionFactory.GetServerConnection(this, args, isConnect: false,
applicationProtocol: sslApplicationProtocol,
noCache: false, cancellationToken: cancellationToken);
//for connection pool, retry fails until cache is exhausted.
// for connection pool, retry fails until cache is exhausted.
return await retryPolicy<ServerConnectionException>().ExecuteAsync(async (connection) =>
{
args.TimeLine["Connection Ready"] = DateTime.Now;
......@@ -390,12 +390,12 @@ namespace Titanium.Web.Proxy
{
var supportedAcceptEncoding = new List<string>();
//only allow proxy supported compressions
// only allow proxy supported compressions
supportedAcceptEncoding.AddRange(acceptEncoding.Split(',')
.Select(x => x.Trim())
.Where(x => ProxyConstants.ProxySupportedCompressions.Contains(x)));
//uncompressed is always supported by proxy
// uncompressed is always supported by proxy
supportedAcceptEncoding.Add("identity");
requestHeaders.SetOrAddHeaderValue(KnownHeaders.AcceptEncoding,
......
......@@ -51,8 +51,8 @@ namespace Titanium.Web.Proxy
}
}
//save original values so that if user changes them
//we can still use original values when syphoning out data from attached tcp connection.
// save original values so that if user changes them
// we can still use original values when syphoning out data from attached tcp connection.
response.SetOriginalHeaders();
// if user requested call back then do it
......@@ -66,10 +66,10 @@ namespace Titanium.Web.Proxy
var clientStreamWriter = args.ProxyClient.ClientStreamWriter;
//user set custom response by ignoring original response from server.
// user set custom response by ignoring original response from server.
if (response.Locked)
{
//write custom user response with body and return.
// write custom user response with body and return.
await clientStreamWriter.WriteResponseAsync(response, cancellationToken: cancellationToken);
if (args.HttpClient.Connection != null
......
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Authentication;
using System.Text;
using Titanium.Web.Proxy.StreamExtended.Models;
......@@ -51,6 +52,33 @@ namespace Titanium.Web.Proxy.StreamExtended
public Dictionary<string, SslExtension> Extensions { get; set; }
public SslProtocols SslProtocol
{
get
{
int major = MajorVersion;
int minor = MinorVersion;
if (major == 3 && minor == 3)
return SslProtocols.Tls12;
if (major == 3 && minor == 2)
return SslProtocols.Tls11;
if (major == 3 && minor == 1)
return SslProtocols.Tls;
#pragma warning disable 618
if (major == 3 && minor == 0)
return SslProtocols.Ssl3;
if (major == 2 && minor == 0)
return SslProtocols.Ssl2;
#pragma warning restore 618
return SslProtocols.None;
}
}
private static string SslVersionToString(int major, int minor)
{
string str = "Unknown";
......@@ -119,4 +147,4 @@ namespace Titanium.Web.Proxy.StreamExtended
return sb.ToString();
}
}
}
\ No newline at end of file
}
using System.Diagnostics;
using System.IO;
using System.Threading;
using Titanium.Web.Proxy.StreamExtended.BufferPool;
namespace Titanium.Web.Proxy.StreamExtended.Network
{
public class ClientHelloAlpnAdderStream : Stream
{
private readonly CustomBufferedStream stream;
private readonly IBufferPool bufferPool;
private bool called;
public ClientHelloAlpnAdderStream(CustomBufferedStream stream, IBufferPool bufferPool)
{
this.stream = stream;
}
public override void Flush()
{
stream.Flush();
}
public override long Seek(long offset, SeekOrigin origin)
{
return stream.Seek(offset, origin);
}
public override void SetLength(long value)
{
stream.SetLength(value);
}
[DebuggerStepThrough]
public override int Read(byte[] buffer, int offset, int count)
{
return stream.Read(buffer, offset, count);
}
public override void Write(byte[] buffer, int offset, int count)
{
if (called)
{
stream.Write(buffer, offset, count);
return;
}
called = true;
var ms = new MemoryStream(buffer, offset, count);
//this can be non async, because reads from a memory stream
var cts = new CancellationTokenSource();
var clientHello = SslTools.PeekClientHello(new CustomBufferedStream(ms, bufferPool, (int)ms.Length), bufferPool, cts.Token).Result;
if (clientHello != null)
{
// 0x00 0x10: ALPN identifier
// 0x00 0x0e: length of ALPN data
// 0x00 0x0c: length of ALPN data again:)
var dataToAdd = new byte[]
{
0x0, 0x10, 0x0, 0xE, 0x0, 0xC,
2, (byte)'h', (byte)'2',
8, (byte)'h', (byte)'t', (byte)'t', (byte)'p', (byte)'/', (byte)'1', (byte)'.', (byte)'1'
};
int newByteCount = clientHello.Extensions == null ? dataToAdd.Length + 2 : dataToAdd.Length;
var buffer2 = new byte[buffer.Length + newByteCount];
for (int i = 0; i < buffer.Length; i++)
{
buffer2[i] = buffer[i];
}
//this is a hacky solution, but works
int length = (buffer[offset + 3] << 8) + buffer[offset + 4];
length += newByteCount;
buffer2[offset + 3] = (byte)(length >> 8);
buffer2[offset + 4] = (byte)length;
length = (buffer[offset + 6] << 16) + (buffer[offset + 7] << 8) + buffer[offset + 8];
length += newByteCount;
buffer2[offset + 6] = (byte)(length >> 16);
buffer2[offset + 7] = (byte)(length >> 8);
buffer2[offset + 8] = (byte)length;
int pos = offset + clientHello.EntensionsStartPosition;
int endPos = offset + clientHello.ClientHelloLength;
if (clientHello.Extensions != null)
{
// update ALPN length
length = (buffer[pos] << 8) + buffer[pos + 1];
length += newByteCount;
buffer2[pos] = (byte)(length >> 8);
buffer2[pos + 1] = (byte)length;
}
else
{
// add ALPN length
length = dataToAdd.Length;
buffer2[pos] = (byte)(length >> 8);
buffer2[pos + 1] = (byte)length;
endPos += 2;
}
for (int i = 0; i < dataToAdd.Length; i++)
{
buffer2[endPos + i] = dataToAdd[i];
}
// copy the reamining data if any
for (int i = clientHello.ClientHelloLength; i < count; i++)
{
buffer2[offset + newByteCount + i] = buffer[offset + i];
}
buffer = buffer2;
count += newByteCount;
}
stream.Write(buffer, offset, count);
}
public override bool CanRead => stream.CanRead;
public override bool CanSeek => stream.CanSeek;
public override bool CanWrite => stream.CanWrite;
public override long Length => stream.Length;
public override long Position
{
get => stream.Position;
set => stream.Position = value;
}
}
}
\ No newline at end of file
......@@ -63,7 +63,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
public void Flush()
{
//send out the current data from from the buffer
// send out the current data from from the buffer
if (bufferLength > 0)
{
writer.Write(buffer, 0, bufferLength);
......@@ -73,7 +73,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
public async Task FlushAsync(CancellationToken cancellationToken = default)
{
//send out the current data from from the buffer
// send out the current data from from the buffer
if (bufferLength > 0)
{
await writer.WriteAsync(buffer, 0, bufferLength, cancellationToken);
......
......@@ -51,7 +51,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
try
{
var method = typeof(NetworkStream).GetMethod(nameof(Stream.ReadAsync),
new Type[] { typeof(byte[]), typeof(int), typeof(int), typeof(CancellationToken) });
new[] { typeof(byte[]), typeof(int), typeof(int), typeof(CancellationToken) });
if (method != null && method.DeclaringType != typeof(Stream))
{
networkStreamHack = false;
......@@ -257,13 +257,13 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
await FillBufferAsync(cancellationToken);
}
//When index is greater than the buffer size
// When index is greater than the buffer size
if (streamBuffer.Length <= index)
{
throw new Exception("Requested Peek index exceeds the buffer size. Consider increasing the buffer size.");
}
//When index is greater than the buffer size
// When index is greater than the buffer size
if (Available <= index)
{
return -1;
......@@ -287,7 +287,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
await FillBufferAsync(cancellationToken);
}
//When index is greater than the buffer size
// When index is greater than the buffer size
if (streamBuffer.Length <= (index + size))
{
throw new Exception("Requested Peek index and size exceeds the buffer size. Consider increasing the buffer size.");
......@@ -477,8 +477,8 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
if (bufferLength > 0)
{
//normally we fill the buffer only when it is empty, but sometimes we need more data
//move the remaining data to the beginning of the buffer
// normally we fill the buffer only when it is empty, but sometimes we need more data
// move the remaining data to the beginning of the buffer
Buffer.BlockCopy(streamBuffer, bufferPos, streamBuffer, 0, bufferLength);
}
......@@ -521,8 +521,8 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
if (bufferLength > 0)
{
//normally we fill the buffer only when it is empty, but sometimes we need more data
//move the remaining data to the beginning of the buffer
// normally we fill the buffer only when it is empty, but sometimes we need more data
// move the remaining data to the beginning of the buffer
Buffer.BlockCopy(streamBuffer, bufferPos, streamBuffer, 0, bufferLength);
}
......@@ -586,7 +586,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
byte newChar = reader.ReadByteFromBuffer();
buffer[bufferDataLength] = newChar;
//if new line
// if new line
if (newChar == '\n')
{
if (lastChar == '\r')
......@@ -599,7 +599,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
bufferDataLength++;
//store last char for new line comparison
// store last char for new line comparison
lastChar = newChar;
if (bufferDataLength == buffer.Length)
......@@ -663,8 +663,8 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
vAsyncResult.ContinueWith(pAsyncResult =>
{
//use TaskExtended to pass State as AsyncObject
//callback will call EndRead (otherwise, it will block)
// use TaskExtended to pass State as AsyncObject
// callback will call EndRead (otherwise, it will block)
callback?.Invoke(new TaskResult<int>(pAsyncResult, state));
});
......@@ -684,8 +684,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
return ((TaskResult<int>)asyncResult).Result;
}
/// <summary>
/// Fix the .net bug with SslStream slow WriteAsync
/// https://github.com/justcoding121/Titanium-Web-Proxy/issues/495
......@@ -709,6 +708,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
return vAsyncResult;
}
public override void EndWrite(IAsyncResult asyncResult)
{
if (!networkStreamHack)
......
using System.Diagnostics;
using System.IO;
using System.Threading;
using Titanium.Web.Proxy.StreamExtended.BufferPool;
namespace Titanium.Web.Proxy.StreamExtended.Network
{
public class ServerHelloAlpnAdderStream : Stream
{
private readonly IBufferPool bufferPool;
private readonly CustomBufferedStream stream;
private bool called;
public ServerHelloAlpnAdderStream(CustomBufferedStream stream, IBufferPool bufferPool)
{
this.bufferPool = bufferPool;
this.stream = stream;
}
public override void Flush()
{
stream.Flush();
}
public override long Seek(long offset, SeekOrigin origin)
{
return stream.Seek(offset, origin);
}
public override void SetLength(long value)
{
stream.SetLength(value);
}
[DebuggerStepThrough]
public override int Read(byte[] buffer, int offset, int count)
{
return stream.Read(buffer, offset, count);
}
public override void Write(byte[] buffer, int offset, int count)
{
if (called)
{
stream.Write(buffer, offset, count);
return;
}
called = true;
var ms = new MemoryStream(buffer, offset, count);
//this can be non async, because reads from a memory stream
var cts = new CancellationTokenSource();
var serverHello = SslTools.PeekServerHello(new CustomBufferedStream(ms, bufferPool, (int)ms.Length), bufferPool, cts.Token).Result;
if (serverHello != null)
{
// 0x00 0x10: ALPN identifier
// 0x00 0x0e: length of ALPN data
// 0x00 0x0c: length of ALPN data again:)
var dataToAdd = new byte[]
{
0x0, 0x10, 0x0, 0x5, 0x0, 0x3,
2, (byte)'h', (byte)'2'
};
int newByteCount = serverHello.Extensions == null ? dataToAdd.Length + 2 : dataToAdd.Length;
var buffer2 = new byte[buffer.Length + newByteCount];
for (int i = 0; i < buffer.Length; i++)
{
buffer2[i] = buffer[i];
}
//this is a hacky solution, but works
int length = (buffer[offset + 3] << 8) + buffer[offset + 4];
length += newByteCount;
buffer2[offset + 3] = (byte)(length >> 8);
buffer2[offset + 4] = (byte)length;
length = (buffer[offset + 6] << 16) + (buffer[offset + 7] << 8) + buffer[offset + 8];
length += newByteCount;
buffer2[offset + 6] = (byte)(length >> 16);
buffer2[offset + 7] = (byte)(length >> 8);
buffer2[offset + 8] = (byte)length;
int pos = offset + serverHello.EntensionsStartPosition;
int endPos = offset + serverHello.ServerHelloLength;
if (serverHello.Extensions != null)
{
// update ALPN length
length = (buffer[pos] << 8) + buffer[pos + 1];
length += newByteCount;
buffer2[pos] = (byte)(length >> 8);
buffer2[pos + 1] = (byte)length;
}
else
{
// add ALPN length
length = dataToAdd.Length;
buffer2[pos] = (byte)(length >> 8);
buffer2[pos + 1] = (byte)length;
endPos += 2;
}
for (int i = 0; i < dataToAdd.Length; i++)
{
buffer2[endPos + i] = dataToAdd[i];
}
// copy the reamining data if any
for (int i = serverHello.ServerHelloLength; i < count; i++)
{
buffer2[offset + newByteCount + i] = buffer[offset + i];
}
buffer = buffer2;
count += newByteCount;
}
stream.Write(buffer, offset, count);
}
public override bool CanRead => stream.CanRead;
public override bool CanSeek => stream.CanSeek;
public override bool CanWrite => stream.CanWrite;
public override long Length => stream.Length;
public override long Position
{
get => stream.Position;
set => stream.Position = value;
}
}
}
\ No newline at end of file
......@@ -16,7 +16,7 @@ namespace Titanium.Web.Proxy.StreamExtended
private static string GetExtensionData(int value, byte[] data)
{
//https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
// https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
switch (value)
{
case 0:
......@@ -59,8 +59,8 @@ namespace Titanium.Web.Proxy.StreamExtended
private static string GetSupportedGroup(byte[] data)
{
//https://datatracker.ietf.org/doc/draft-ietf-tls-rfc4492bis/?include_text=1
List<string> list = new List<string>();
// https://datatracker.ietf.org/doc/draft-ietf-tls-rfc4492bis/?include_text=1
var list = new List<string>();
if (data.Length < 2)
{
return string.Empty;
......@@ -73,70 +73,70 @@ namespace Titanium.Web.Proxy.StreamExtended
switch (namedCurve)
{
case 1:
list.Add("sect163k1 [0x1]"); //deprecated
list.Add("sect163k1 [0x1]"); // deprecated
break;
case 2:
list.Add("sect163r1 [0x2]"); //deprecated
list.Add("sect163r1 [0x2]"); // deprecated
break;
case 3:
list.Add("sect163r2 [0x3]"); //deprecated
list.Add("sect163r2 [0x3]"); // deprecated
break;
case 4:
list.Add("sect193r1 [0x4]"); //deprecated
list.Add("sect193r1 [0x4]"); // deprecated
break;
case 5:
list.Add("sect193r2 [0x5]"); //deprecated
list.Add("sect193r2 [0x5]"); // deprecated
break;
case 6:
list.Add("sect233k1 [0x6]"); //deprecated
list.Add("sect233k1 [0x6]"); // deprecated
break;
case 7:
list.Add("sect233r1 [0x7]"); //deprecated
list.Add("sect233r1 [0x7]"); // deprecated
break;
case 8:
list.Add("sect239k1 [0x8]"); //deprecated
list.Add("sect239k1 [0x8]"); // deprecated
break;
case 9:
list.Add("sect283k1 [0x9]"); //deprecated
list.Add("sect283k1 [0x9]"); // deprecated
break;
case 10:
list.Add("sect283r1 [0xA]"); //deprecated
list.Add("sect283r1 [0xA]"); // deprecated
break;
case 11:
list.Add("sect409k1 [0xB]"); //deprecated
list.Add("sect409k1 [0xB]"); // deprecated
break;
case 12:
list.Add("sect409r1 [0xC]"); //deprecated
list.Add("sect409r1 [0xC]"); // deprecated
break;
case 13:
list.Add("sect571k1 [0xD]"); //deprecated
list.Add("sect571k1 [0xD]"); // deprecated
break;
case 14:
list.Add("sect571r1 [0xE]"); //deprecated
list.Add("sect571r1 [0xE]"); // deprecated
break;
case 15:
list.Add("secp160k1 [0xF]"); //deprecated
list.Add("secp160k1 [0xF]"); // deprecated
break;
case 16:
list.Add("secp160r1 [0x10]"); //deprecated
list.Add("secp160r1 [0x10]"); // deprecated
break;
case 17:
list.Add("secp160r2 [0x11]"); //deprecated
list.Add("secp160r2 [0x11]"); // deprecated
break;
case 18:
list.Add("secp192k1 [0x12]"); //deprecated
list.Add("secp192k1 [0x12]"); // deprecated
break;
case 19:
list.Add("secp192r1 [0x13]"); //deprecated
list.Add("secp192r1 [0x13]"); // deprecated
break;
case 20:
list.Add("secp224k1 [0x14]"); //deprecated
list.Add("secp224k1 [0x14]"); // deprecated
break;
case 21:
list.Add("secp224r1 [0x15]"); //deprecated
list.Add("secp224r1 [0x15]"); // deprecated
break;
case 22:
list.Add("secp256k1 [0x16]"); //deprecated
list.Add("secp256k1 [0x16]"); // deprecated
break;
case 23:
list.Add("secp256r1 [0x17]");
......@@ -178,10 +178,10 @@ namespace Titanium.Web.Proxy.StreamExtended
list.Add("ffdhe8192 [0x0104]");
break;
case 65281:
list.Add("arbitrary_explicit_prime_curves [0xFF01]"); //deprecated
list.Add("arbitrary_explicit_prime_curves [0xFF01]"); // deprecated
break;
case 65282:
list.Add("arbitrary_explicit_char2_curves [0xFF02]"); //deprecated
list.Add("arbitrary_explicit_char2_curves [0xFF02]"); // deprecated
break;
default:
list.Add($"unknown [0x{namedCurve:X4}]");
......@@ -318,7 +318,7 @@ namespace Titanium.Web.Proxy.StreamExtended
private static string GetExtensionName(int value)
{
//https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
// https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
switch (value)
{
case 0:
......
......@@ -34,8 +34,8 @@ namespace Titanium.Web.Proxy.StreamExtended
/// <returns></returns>
public static async Task<ClientHelloInfo> PeekClientHello(CustomBufferedStream clientStream, IBufferPool bufferPool, CancellationToken cancellationToken = default)
{
//detects the HTTPS ClientHello message as it is described in the following url:
//https://stackoverflow.com/questions/3897883/how-to-detect-an-incoming-ssl-https-handshake-ssl-wire-format
// detects the HTTPS ClientHello message as it is described in the following url:
// https://stackoverflow.com/questions/3897883/how-to-detect-an-incoming-ssl-https-handshake-ssl-wire-format
int recordType = await clientStream.PeekByteAsync(0, cancellationToken);
if (recordType == -1)
......@@ -45,7 +45,7 @@ namespace Titanium.Web.Proxy.StreamExtended
if ((recordType & 0x80) == 0x80)
{
//SSL 2
// SSL 2
var peekStream = new CustomBufferedPeekStream(clientStream, bufferPool, 1);
// length value + minimum length
......@@ -105,14 +105,14 @@ namespace Titanium.Web.Proxy.StreamExtended
{
var peekStream = new CustomBufferedPeekStream(clientStream, bufferPool, 1);
//should contain at least 43 bytes
// should contain at least 43 bytes
// 2 version + 2 length + 1 type + 3 length(?) + 2 version + 32 random + 1 sessionid length
if (!await peekStream.EnsureBufferLength(43, cancellationToken))
{
return null;
}
//SSL 3.0 or TLS 1.0, 1.1 and 1.2
// SSL 3.0 or TLS 1.0, 1.1 and 1.2
int majorVersion = peekStream.ReadByte();
int minorVersion = peekStream.ReadByte();
......@@ -220,8 +220,8 @@ namespace Titanium.Web.Proxy.StreamExtended
/// <returns></returns>
public static async Task<ServerHelloInfo> PeekServerHello(CustomBufferedStream serverStream, IBufferPool bufferPool, CancellationToken cancellationToken = default)
{
//detects the HTTPS ClientHello message as it is described in the following url:
//https://stackoverflow.com/questions/3897883/how-to-detect-an-incoming-ssl-https-handshake-ssl-wire-format
// detects the HTTPS ClientHello message as it is described in the following url:
// https://stackoverflow.com/questions/3897883/how-to-detect-an-incoming-ssl-https-handshake-ssl-wire-format
int recordType = await serverStream.PeekByteAsync(0, cancellationToken);
if (recordType == -1)
......@@ -231,7 +231,7 @@ namespace Titanium.Web.Proxy.StreamExtended
if ((recordType & 0x80) == 0x80)
{
//SSL 2
// SSL 2
// not tested. SSL2 is deprecated
var peekStream = new CustomBufferedPeekStream(serverStream, bufferPool, 1);
......@@ -284,14 +284,14 @@ namespace Titanium.Web.Proxy.StreamExtended
{
var peekStream = new CustomBufferedPeekStream(serverStream, bufferPool, 1);
//should contain at least 43 bytes
// should contain at least 43 bytes
// 2 version + 2 length + 1 type + 3 length(?) + 2 version + 32 random + 1 sessionid length
if (!await peekStream.EnsureBufferLength(43, cancellationToken))
{
return null;
}
//SSL 3.0 or TLS 1.0, 1.1 and 1.2
// SSL 3.0 or TLS 1.0, 1.1 and 1.2
int majorVersion = peekStream.ReadByte();
int minorVersion = peekStream.ReadByte();
......
......@@ -62,8 +62,9 @@ namespace Titanium.Web.Proxy
if (endPoint.DecryptSsl && args.DecryptSsl)
{
clientConnection.SslProtocol = clientHelloInfo.SslProtocol;
//do client authentication using certificate
// do client authentication using certificate
X509Certificate2 certificate = null;
try
{
......
......@@ -13,7 +13,7 @@ using System.Threading.Tasks;
namespace Titanium.Web.Proxy.IntegrationTests.Setup
{
//set up a kestrel test server
// set up a kestrel test server
public class TestServer : IDisposable
{
public string ListeningHttpUrl => $"http://localhost:{HttpListeningPort}";
......
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