Commit bff73ef4 authored by justcoding121's avatar justcoding121 Committed by justcoding121

Merge pull request #278 from honfika/develop

Detect HTTPS ClientHello instead of manually listing the HTTPS ports +bugfix
parents 70c107a4 3ab43a56
......@@ -16,8 +16,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
//share requestBody outside handlers
//Using a dictionary is not a good idea since it can cause memory overflow
//ideally the data should be moved out of memory
//private readonly IDictionary<Guid, string> requestBodyHistory
// = new ConcurrentDictionary<Guid, string>();
//private readonly IDictionary<Guid, string> requestBodyHistory = new ConcurrentDictionary<Guid, string>();
public ProxyTestController()
{
......@@ -44,6 +43,8 @@ namespace Titanium.Web.Proxy.Examples.Basic
{
proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse;
proxyServer.TunnelConnectRequest += OnTunnelConnectRequest;
proxyServer.TunnelConnectResponse += OnTunnelConnectResponse;
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
......@@ -57,11 +58,14 @@ namespace Titanium.Web.Proxy.Examples.Basic
ExcludedHttpsHostNameRegex = new List<string>
{
"dropbox.com"
}
},
//Include Https addresses you want to proxy (others will be excluded)
//for example github.com
//IncludedHttpsHostNameRegex = new List<string> { "github.com" }
//IncludedHttpsHostNameRegex = new List<string>
//{
// "github.com"
//},
//You can set only one of the ExcludedHttpsHostNameRegex and IncludedHttpsHostNameRegex properties, otherwise ArgumentException will be thrown
......@@ -103,6 +107,8 @@ namespace Titanium.Web.Proxy.Examples.Basic
public void Stop()
{
proxyServer.TunnelConnectRequest -= OnTunnelConnectRequest;
proxyServer.TunnelConnectResponse -= OnTunnelConnectResponse;
proxyServer.BeforeRequest -= OnRequest;
proxyServer.BeforeResponse -= OnResponse;
proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation;
......@@ -114,6 +120,15 @@ namespace Titanium.Web.Proxy.Examples.Basic
//proxyServer.CertificateManager.RemoveTrustedRootCertificates();
}
private async Task OnTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
{
Console.WriteLine("Tunnel to: " + e.WebSession.Request.Host);
}
private async Task OnTunnelConnectResponse(object sender, TunnelConnectSessionEventArgs e)
{
}
//intecept & cancel redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e)
{
......
......@@ -434,7 +434,10 @@ namespace Titanium.Web.Proxy.EventArguments
if (headers != null && headers.Count > 0)
{
response.ResponseHeaders = headers;
foreach (var header in headers)
{
response.ResponseHeaders.AddHeader(header.Key, header.Value.Value);
}
}
response.HttpVersion = WebSession.Request.HttpVersion;
......@@ -501,7 +504,10 @@ namespace Titanium.Web.Proxy.EventArguments
if (headers != null && headers.Count > 0)
{
response.ResponseHeaders = headers;
foreach (var header in headers)
{
response.ResponseHeaders.AddHeader(header.Key, header.Value.Value);
}
}
response.HttpVersion = WebSession.Request.HttpVersion;
......@@ -523,7 +529,7 @@ namespace Titanium.Web.Proxy.EventArguments
var response = new RedirectResponse();
response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseHeaders.Add("Location", new HttpHeader("Location", url));
response.ResponseHeaders.AddHeader("Location", url);
response.ResponseBody = Encoding.ASCII.GetBytes(string.Empty);
await Respond(response);
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.EventArguments
{
public class TunnelConnectSessionEventArgs : SessionEventArgs
{
public bool IsHttps { get; set; }
public TunnelConnectSessionEventArgs() : base(0, null)
{
}
}
}
......@@ -306,6 +306,21 @@ namespace Titanium.Web.Proxy.Helpers
return streamBuffer[bufferPos++];
}
public async Task<int> PeekByteAsync(int index)
{
if (Available <= index)
{
await FillBufferAsync();
}
if (Available <= index)
{
return -1;
}
return streamBuffer[bufferPos + index];
}
public byte ReadByteFromBuffer()
{
if (bufferLength == 0)
......@@ -396,6 +411,8 @@ namespace Titanium.Web.Proxy.Helpers
public bool DataAvailable => bufferLength > 0;
public int Available => bufferLength;
/// <summary>
/// When overridden in a derived class, gets or sets the position within the current stream.
/// </summary>
......@@ -428,14 +445,22 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary>
public bool FillBuffer()
{
bufferLength = baseStream.Read(streamBuffer, 0, streamBuffer.Length);
bufferPos = 0;
if (bufferLength > 0)
{
OnDataReceived(streamBuffer, 0, bufferLength);
//normally we fill the buffer only when it is empty, but sometimes we need more data
//move the remanining data to the beginning of the buffer
Buffer.BlockCopy(streamBuffer, bufferPos, streamBuffer, 0, bufferLength);
}
bufferPos = 0;
int readBytes = baseStream.Read(streamBuffer, bufferLength, streamBuffer.Length - bufferLength);
if (readBytes > 0)
{
OnDataReceived(streamBuffer, bufferLength, readBytes);
bufferLength += readBytes;
}
return bufferLength > 0;
return readBytes > 0;
}
/// <summary>
......@@ -454,14 +479,22 @@ namespace Titanium.Web.Proxy.Helpers
/// <returns></returns>
public async Task<bool> FillBufferAsync(CancellationToken cancellationToken)
{
bufferLength = await baseStream.ReadAsync(streamBuffer, 0, streamBuffer.Length, cancellationToken);
bufferPos = 0;
if (bufferLength > 0)
{
OnDataReceived(streamBuffer, 0, bufferLength);
//normally we fill the buffer only when it is empty, but sometimes we need more data
//move the remanining data to the beginning of the buffer
Buffer.BlockCopy(streamBuffer, bufferPos, streamBuffer, 0, bufferLength);
}
bufferPos = 0;
int readBytes = await baseStream.ReadAsync(streamBuffer, bufferLength, streamBuffer.Length - bufferLength, cancellationToken);
if (readBytes > 0)
{
OnDataReceived(streamBuffer, bufferLength, readBytes);
bufferLength += readBytes;
}
return bufferLength > 0;
return readBytes > 0;
}
private class ReadAsyncResult : IAsyncResult
......
......@@ -170,20 +170,12 @@ namespace Titanium.Web.Proxy.Helpers
/// 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="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="clientStream"></param>
/// <param name="tcpConnectionFactory"></param>
/// <param name="connection"></param>
/// <returns></returns>
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,
TcpConnection connection = null)
internal static async Task SendRaw(string httpCmd, IEnumerable<HttpHeader> requestHeaders, Stream clientStream, TcpConnection connection)
{
//prepare the prefix content
StringBuilder sb = null;
......@@ -199,7 +191,7 @@ namespace Titanium.Web.Proxy.Helpers
if (requestHeaders != null)
{
foreach (string header in requestHeaders.Select(t => t.Value.ToString()))
foreach (string header in requestHeaders.Select(t => t.ToString()))
{
sb.Append(header);
sb.Append(ProxyConstants.NewLine);
......@@ -209,24 +201,7 @@ namespace Titanium.Web.Proxy.Helpers
sb.Append(ProxyConstants.NewLine);
}
bool connectionCreated = false;
TcpConnection tcpConnection;
//create new connection if connection is null
if (connection == null)
{
tcpConnection = await tcpConnectionFactory.CreateClient(server, remoteHostName, remotePort, httpVersion, isHttps, null, null);
connectionCreated = true;
}
else
{
tcpConnection = connection;
}
try
{
var tunnelStream = tcpConnection.Stream;
var tunnelStream = connection.Stream;
//Now async relay all server=>client & client=>server data
var sendRelay = clientStream.CopyToAsync(sb?.ToString() ?? string.Empty, tunnelStream);
......@@ -235,17 +210,5 @@ namespace Titanium.Web.Proxy.Helpers
await Task.WhenAll(sendRelay, receiveRelay);
}
finally
{
//if connection was null
//then a new connection was created
//so dispose the new connection
if (connectionCreated)
{
tcpConnection.Dispose();
Interlocked.Decrement(ref server.serverConnectionCount);
}
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Http
{
public class ConnectRequest : Request
{
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Http
{
public class ConnectResponse : Response
{
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Http
{
public class HeaderCollection : IEnumerable<HttpHeader>
{
/// <summary>
/// Unique Request header collection
/// </summary>
public Dictionary<string, HttpHeader> Headers { get; set; }
/// <summary>
/// Non Unique headers
/// </summary>
public Dictionary<string, List<HttpHeader>> NonUniqueHeaders { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="HeaderCollection"/> class.
/// </summary>
public HeaderCollection()
{
Headers = new Dictionary<string, HttpHeader>(StringComparer.OrdinalIgnoreCase);
NonUniqueHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// True if header exists
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public bool HeaderExists(string name)
{
return Headers.ContainsKey(name) || NonUniqueHeaders.ContainsKey(name);
}
/// <summary>
/// Returns all headers with given name if exists
/// Returns null if does'nt exist
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public List<HttpHeader> GetHeaders(string name)
{
if (Headers.ContainsKey(name))
{
return new List<HttpHeader>
{
Headers[name]
};
}
if (NonUniqueHeaders.ContainsKey(name))
{
return new List<HttpHeader>(NonUniqueHeaders[name]);
}
return null;
}
/// <summary>
/// Returns all headers
/// </summary>
/// <returns></returns>
public List<HttpHeader> GetAllHeaders()
{
var result = new List<HttpHeader>();
result.AddRange(Headers.Select(x => x.Value));
result.AddRange(NonUniqueHeaders.SelectMany(x => x.Value));
return result;
}
/// <summary>
/// Add a new header with given name and value
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
public void AddHeader(string name, string value)
{
AddHeader(new HttpHeader(name, value));
}
/// <summary>
/// Adds the given header object to Request
/// </summary>
/// <param name="newHeader"></param>
public void AddHeader(HttpHeader newHeader)
{
if (NonUniqueHeaders.ContainsKey(newHeader.Name))
{
NonUniqueHeaders[newHeader.Name].Add(newHeader);
return;
}
if (Headers.ContainsKey(newHeader.Name))
{
var existing = Headers[newHeader.Name];
Headers.Remove(newHeader.Name);
NonUniqueHeaders.Add(newHeader.Name, new List<HttpHeader>
{
existing,
newHeader
});
}
else
{
Headers.Add(newHeader.Name, newHeader);
}
}
/// <summary>
/// removes all headers with given name
/// </summary>
/// <param name="headerName"></param>
/// <returns>True if header was removed
/// False if no header exists with given name</returns>
public bool RemoveHeader(string headerName)
{
bool result = Headers.Remove(headerName);
// do not convert to '||' expression to avoid lazy evaluation
if (NonUniqueHeaders.Remove(headerName))
{
result = true;
}
return result;
}
/// <summary>
/// Removes given header object if it exist
/// </summary>
/// <param name="header">Returns true if header exists and was removed </param>
public bool RemoveHeader(HttpHeader header)
{
if (Headers.ContainsKey(header.Name))
{
if (Headers[header.Name].Equals(header))
{
Headers.Remove(header.Name);
return true;
}
}
else if (NonUniqueHeaders.ContainsKey(header.Name))
{
if (NonUniqueHeaders[header.Name].RemoveAll(x => x.Equals(header)) > 0)
{
return true;
}
}
return false;
}
internal string GetHeaderValueOrNull(string headerName)
{
HttpHeader header;
if (Headers.TryGetValue(headerName, out header))
{
return header.Value;
}
return null;
}
internal string SetOrAddHeaderValue(string headerName, string value)
{
HttpHeader header;
if (Headers.TryGetValue(headerName, out header))
{
header.Value = value;
}
else
{
Headers.Add(headerName, new HttpHeader(headerName, value));
}
return null;
}
/// <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<HttpHeader> GetEnumerator()
{
return Headers.Values.Concat(NonUniqueHeaders.Values.SelectMany(x => x)).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
......@@ -8,9 +8,11 @@ namespace Titanium.Web.Proxy.Http
{
internal static class HeaderParser
{
internal static async Task ReadHeaders(CustomBinaryReader reader, Dictionary<string, List<HttpHeader>> nonUniqueResponseHeaders,
Dictionary<string, HttpHeader> headers)
internal static async Task ReadHeaders(CustomBinaryReader reader, HeaderCollection headerCollection)
{
var nonUniqueResponseHeaders = headerCollection.NonUniqueHeaders;
var headers = headerCollection.Headers;
string tmpLine;
while (!string.IsNullOrEmpty(tmpLine = await reader.ReadLineAsync()))
{
......
......@@ -27,7 +27,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Headers passed with Connect.
/// </summary>
public List<HttpHeader> ConnectHeaders { get; set; }
public ConnectRequest ConnectRequest { get; set; }
/// <summary>
/// Web Request.
......@@ -94,28 +94,14 @@ namespace Titanium.Web.Proxy.Http
$"{ServerConnection.UpStreamHttpProxy.UserName}:{ServerConnection.UpStreamHttpProxy.Password}")));
}
//write request headers
foreach (var headerItem in Request.RequestHeaders)
foreach (var header in Request.RequestHeaders)
{
var header = headerItem.Value;
if (headerItem.Key != "Proxy-Authorization")
if (header.Name != "Proxy-Authorization")
{
requestLines.AppendLine($"{header.Name}: {header.Value}");
}
}
//write non unique request headers
foreach (var headerItem in Request.NonUniqueRequestHeaders)
{
var headers = headerItem.Value;
foreach (var header in headers)
{
if (headerItem.Key != "Proxy-Authorization")
{
requestLines.AppendLine($"{header.Name}: {header.Value}");
}
}
}
requestLines.AppendLine();
string request = requestLines.ToString();
......@@ -211,7 +197,7 @@ namespace Titanium.Web.Proxy.Http
}
//Read the response headers in to unique and non-unique header collections
await HeaderParser.ReadHeaders(ServerConnection.StreamReader, Response.NonUniqueResponseHeaders, Response.ResponseHeaders);
await HeaderParser.ReadHeaders(ServerConnection.StreamReader, Response.ResponseHeaders);
}
/// <summary>
......@@ -219,7 +205,7 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
public void Dispose()
{
ConnectHeaders = null;
ConnectRequest = null;
Request.Dispose();
Response.Dispose();
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Http
{
class HttpsTools
{
public static async Task<bool> IsClientHello(CustomBufferedStream clientStream)
{
//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);
if (recordType == 0x80)
{
//SSL 2
int length = await clientStream.PeekByteAsync(1);
if (length < 9)
{
// Message body too short.
return false;
}
if (await clientStream.PeekByteAsync(2) != 0x01)
{
// should be ClientHello
return false;
}
int majorVersion = await clientStream.PeekByteAsync(3);
int minorVersion = await clientStream.PeekByteAsync(4);
return true;
}
else if (recordType == 0x16)
{
//SSL 3.0 or TLS 1.0, 1.1 and 1.2
int majorVersion = await clientStream.PeekByteAsync(1);
int minorVersion = await clientStream.PeekByteAsync(2);
int length1 = await clientStream.PeekByteAsync(3);
int length2 = await clientStream.PeekByteAsync(4);
int length = (length1 << 8) + length2;
if (await clientStream.PeekByteAsync(5) != 0x01)
{
// should be ClientHello
return false;
}
return true;
}
return false;
}
}
}
This diff is collapsed.
This diff is collapsed.
......@@ -53,6 +53,7 @@ namespace Titanium.Web.Proxy.Models
public bool IpV6Enabled => Equals(IpAddress, IPAddress.IPv6Any)
|| Equals(IpAddress, IPAddress.IPv6Loopback)
|| Equals(IpAddress, IPAddress.IPv6None);
}
/// <summary>
......@@ -68,12 +69,6 @@ namespace Titanium.Web.Proxy.Models
internal bool IsSystemHttpsProxy { get; set; }
/// <summary>
/// Remote HTTPS ports we are allowed to communicate with
/// CONNECT request to ports other than these will not be decrypted
/// </summary>
public List<int> RemoteHttpsPorts { get; set; }
/// <summary>
/// List of host names to exclude using Regular Expressions.
/// </summary>
......@@ -121,12 +116,6 @@ namespace Titanium.Web.Proxy.Models
/// <param name="enableSsl"></param>
public ExplicitProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl) : base(ipAddress, port, enableSsl)
{
//init to well known HTTPS ports
RemoteHttpsPorts = new List<int>
{
443,
8443
};
}
}
......
......@@ -15,12 +15,16 @@ namespace Titanium.Web.Proxy.Network.Tcp
internal ExternalProxy UpStreamHttpsProxy { get; set; }
internal ExternalProxy UpStreamProxy => UseProxy ? IsHttps ? UpStreamHttpsProxy : UpStreamHttpProxy : null;
internal string HostName { get; set; }
internal int Port { get; set; }
internal bool IsHttps { get; set; }
internal bool UseProxy { get; set; }
/// <summary>
/// Http version
/// </summary>
......
......@@ -32,29 +32,18 @@ namespace Titanium.Web.Proxy.Network.Tcp
internal async Task<TcpConnection> CreateClient(ProxyServer server, string remoteHostName, int remotePort, Version httpVersion, bool isHttps,
ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy)
{
bool useHttpProxy = false;
//check if external proxy is set for HTTP
if (!isHttps && externalHttpProxy != null && !(externalHttpProxy.HostName == remoteHostName && externalHttpProxy.Port == remotePort))
{
useHttpProxy = true;
//check if we need to ByPass
if (externalHttpProxy.BypassLocalhost && NetworkHelper.IsLocalIpAddress(remoteHostName))
{
useHttpProxy = false;
}
}
bool useProxy = false;
var externalProxy = isHttps ? externalHttpsProxy : externalHttpProxy;
bool useHttpsProxy = false;
//check if external proxy is set for HTTPS
if (isHttps && externalHttpsProxy != null && !(externalHttpsProxy.HostName == remoteHostName && externalHttpsProxy.Port == remotePort))
//check if external proxy is set for HTTP/HTTPS
if (externalProxy != null && !(externalProxy.HostName == remoteHostName && externalProxy.Port == remotePort))
{
useHttpsProxy = true;
useProxy = true;
//check if we need to ByPass
if (externalHttpsProxy.BypassLocalhost && NetworkHelper.IsLocalIpAddress(remoteHostName))
if (externalProxy.BypassLocalhost && NetworkHelper.IsLocalIpAddress(remoteHostName))
{
useHttpsProxy = false;
useProxy = false;
}
}
......@@ -63,13 +52,11 @@ namespace Titanium.Web.Proxy.Network.Tcp
try
{
if (isHttps)
{
//If this proxy uses another external proxy then create a tunnel request for HTTPS connections
if (useHttpsProxy)
//If this proxy uses another external proxy then create a tunnel request for HTTP/HTTPS connections
if (useProxy)
{
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port);
await client.ConnectAsync(externalProxy.HostName, externalProxy.Port);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
using (var writer = new StreamWriter(stream, Encoding.ASCII, server.BufferSize, true)
......@@ -81,12 +68,12 @@ namespace Titanium.Web.Proxy.Network.Tcp
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
await writer.WriteLineAsync("Connection: Keep-Alive");
if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null)
if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
{
await writer.WriteLineAsync("Proxy-Connection: keep-alive");
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " +
Convert.ToBase64String(Encoding.UTF8.GetBytes(
externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password)));
externalProxy.UserName + ":" + externalProxy.Password)));
}
await writer.WriteLineAsync();
await writer.FlushAsync();
......@@ -112,26 +99,13 @@ namespace Titanium.Web.Proxy.Network.Tcp
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
if (isHttps)
{
var sslStream = new SslStream(stream, false, server.ValidateServerCertificate, server.SelectClientCertificate);
stream = new CustomBufferedStream(sslStream, server.BufferSize);
await sslStream.AuthenticateAsClientAsync(remoteHostName, null, server.SupportedSslProtocols, server.CheckCertificateRevocation);
}
else
{
if (useHttpProxy)
{
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(externalHttpProxy.HostName, externalHttpProxy.Port);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
else
{
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(remoteHostName, remotePort);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
}
client.ReceiveTimeout = server.ConnectionTimeOutSeconds * 1000;
client.SendTimeout = server.ConnectionTimeOutSeconds * 1000;
......@@ -152,6 +126,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
HostName = remoteHostName,
Port = remotePort,
IsHttps = isHttps,
UseProxy = useProxy,
TcpClient = client,
StreamReader = new CustomBinaryReader(stream, server.BufferSize),
Stream = stream,
......
......@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
......@@ -12,31 +13,29 @@ namespace Titanium.Web.Proxy
{
public partial class ProxyServer
{
private async Task<bool> CheckAuthorization(StreamWriter clientStreamWriter, IEnumerable<HttpHeader> headers)
private async Task<bool> CheckAuthorization(StreamWriter clientStreamWriter, SessionEventArgs session)
{
if (AuthenticateUserFunc == null)
{
return true;
}
var httpHeaders = headers as ICollection<HttpHeader> ?? headers.ToArray();
var httpHeaders = session.WebSession.Request.RequestHeaders.ToArray();
try
{
if (httpHeaders.All(t => t.Name != "Proxy-Authorization"))
var header = httpHeaders.FirstOrDefault(t => t.Name == "Proxy-Authorization");
if (header == null)
{
await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Required");
session.WebSession.Response = await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Required");
return false;
}
var header = httpHeaders.FirstOrDefault(t => t.Name == "Proxy-Authorization");
if (header == null)
throw new NullReferenceException();
string headerValue = header.Value.Trim();
if (!headerValue.StartsWith("basic", StringComparison.CurrentCultureIgnoreCase))
{
//Return not authorized
await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
session.WebSession.Response = await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
return false;
}
......@@ -46,7 +45,7 @@ namespace Titanium.Web.Proxy
if (decoded.Contains(":") == false)
{
//Return not authorized
await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
session.WebSession.Response = await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
return false;
}
......@@ -59,25 +58,25 @@ namespace Titanium.Web.Proxy
ExceptionFunc(new ProxyAuthorizationException("Error whilst authorizing request", e, httpHeaders));
//Return not authorized
await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
session.WebSession.Response = await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
return false;
}
}
private async Task SendAuthentication407Response(StreamWriter clientStreamWriter, string description)
private async Task<Response> SendAuthentication407Response(StreamWriter clientStreamWriter, string description)
{
await WriteResponseStatus(HttpHeader.Version11, "407", description, clientStreamWriter);
var response = new Response
{
ResponseHeaders = new Dictionary<string, HttpHeader>
{
{ "Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"") },
{ "Proxy-Connection", new HttpHeader("Proxy-Connection", "close") }
}
HttpVersion = HttpHeader.Version11,
ResponseStatusCode = "407",
ResponseStatusDescription = description
};
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
response.ResponseHeaders.AddHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"");
response.ResponseHeaders.AddHeader("Proxy-Connection", "close");
await WriteResponse(response, clientStreamWriter);
return response;
}
}
}
......@@ -172,6 +172,16 @@ namespace Titanium.Web.Proxy
/// </summary>
public event Func<object, SessionEventArgs, Task> BeforeResponse;
/// <summary>
/// Intercept tunnel connect reques
/// </summary>
public event Func<object, TunnelConnectSessionEventArgs, Task> TunnelConnectRequest;
/// <summary>
/// Intercept tunnel connect response
/// </summary>
public event Func<object, TunnelConnectSessionEventArgs, Task> TunnelConnectResponse;
/// <summary>
/// External proxy for Http
/// </summary>
......
This diff is collapsed.
......@@ -149,6 +149,19 @@ namespace Titanium.Web.Proxy
return await compressor.Compress(responseBodyStream);
}
/// <summary>
/// Writes the response.
/// </summary>
/// <param name="response"></param>
/// <param name="responseWriter"></param>
/// <param name="flush"></param>
/// <returns></returns>
private async Task WriteResponse(Response response, StreamWriter responseWriter, bool flush = true)
{
await WriteResponseStatus(response.HttpVersion, response.ResponseStatusCode, response.ResponseStatusDescription, responseWriter);
await WriteResponseHeaders(responseWriter, response, flush);
}
/// <summary>
/// Write response status
/// </summary>
......@@ -167,54 +180,37 @@ namespace Titanium.Web.Proxy
/// </summary>
/// <param name="responseWriter"></param>
/// <param name="response"></param>
/// <param name="flush"></param>
/// <returns></returns>
private async Task WriteResponseHeaders(StreamWriter responseWriter, Response response)
private async Task WriteResponseHeaders(StreamWriter responseWriter, Response response, bool flush = true)
{
FixProxyHeaders(response.ResponseHeaders);
foreach (var header in response.ResponseHeaders)
{
await header.Value.WriteToStream(responseWriter);
}
//write non unique request headers
foreach (var headerItem in response.NonUniqueResponseHeaders)
{
var headers = headerItem.Value;
foreach (var header in headers)
{
await header.WriteToStream(responseWriter);
}
}
await responseWriter.WriteLineAsync();
if (flush)
{
await responseWriter.FlushAsync();
}
}
/// <summary>
/// Fix proxy specific headers
/// </summary>
/// <param name="headers"></param>
private void FixProxyHeaders(Dictionary<string, HttpHeader> headers)
private void FixProxyHeaders(HeaderCollection headers)
{
//If proxy-connection close was returned inform to close the connection
bool hasProxyHeader = headers.ContainsKey("proxy-connection");
bool hasConnectionheader = headers.ContainsKey("connection");
string proxyHeader = headers.GetHeaderValueOrNull("proxy-connection");
headers.RemoveHeader("proxy-connection");
if (hasProxyHeader)
{
var proxyHeader = headers["proxy-connection"];
if (hasConnectionheader == false)
if (proxyHeader != null)
{
headers.Add("connection", new HttpHeader("connection", proxyHeader.Value));
}
else
{
var connectionHeader = headers["connection"];
connectionHeader.Value = proxyHeader.Value;
}
headers.Remove("proxy-connection");
headers.SetOrAddHeaderValue("connection", proxyHeader);
}
}
......
......@@ -73,6 +73,7 @@
<Compile Include="Decompression\IDecompression.cs" />
<Compile Include="EventArguments\CertificateSelectionEventArgs.cs" />
<Compile Include="EventArguments\CertificateValidationEventArgs.cs" />
<Compile Include="EventArguments\TunnelConnectEventArgs.cs" />
<Compile Include="Extensions\ByteArrayExtensions.cs" />
<Compile Include="Extensions\FuncExtensions.cs" />
<Compile Include="Helpers\HttpHelper.cs" />
......@@ -85,7 +86,11 @@
<Compile Include="Helpers\RunTime.cs" />
<Compile Include="Helpers\WinHttp\WinHttpHandle.cs" />
<Compile Include="Helpers\WinHttp\WinHttpWebProxyFinder.cs" />
<Compile Include="Http\ConnectRequest.cs" />
<Compile Include="Http\ConnectResponse.cs" />
<Compile Include="Http\HeaderCollection.cs" />
<Compile Include="Http\HeaderParser.cs" />
<Compile Include="Http\HttpsTools.cs" />
<Compile Include="Http\Responses\GenericResponse.cs" />
<Compile Include="Network\CachedCertificate.cs" />
<Compile Include="Network\Certificate\WinCertificateMaker.cs" />
......
......@@ -44,7 +44,7 @@ namespace Titanium.Web.Proxy
//check in non-unique headers first
var header =
args.WebSession.Response.NonUniqueResponseHeaders.FirstOrDefault(
args.WebSession.Response.ResponseHeaders.NonUniqueHeaders.FirstOrDefault(
x => authHeaderNames.Any(y => x.Key.Equals(y, StringComparison.OrdinalIgnoreCase)));
if (!header.Equals(new KeyValuePair<string, List<HttpHeader>>()))
......@@ -54,7 +54,7 @@ namespace Titanium.Web.Proxy
if (headerName != null)
{
authHeader = args.WebSession.Response.NonUniqueResponseHeaders[headerName]
authHeader = args.WebSession.Response.ResponseHeaders.NonUniqueHeaders[headerName]
.FirstOrDefault(x => authSchemes.Any(y => x.Value.StartsWith(y, StringComparison.OrdinalIgnoreCase)));
}
......@@ -63,7 +63,7 @@ namespace Titanium.Web.Proxy
{
//check in non-unique headers first
var uHeader =
args.WebSession.Response.ResponseHeaders.FirstOrDefault(x => authHeaderNames.Any(y => x.Key.Equals(y, StringComparison.OrdinalIgnoreCase)));
args.WebSession.Response.ResponseHeaders.Headers.FirstOrDefault(x => authHeaderNames.Any(y => x.Key.Equals(y, StringComparison.OrdinalIgnoreCase)));
if (!uHeader.Equals(new KeyValuePair<string, HttpHeader>()))
{
......@@ -72,9 +72,9 @@ namespace Titanium.Web.Proxy
if (headerName != null)
{
authHeader = authSchemes.Any(x => args.WebSession.Response.ResponseHeaders[headerName].Value
authHeader = authSchemes.Any(x => args.WebSession.Response.ResponseHeaders.Headers[headerName].Value
.StartsWith(x, StringComparison.OrdinalIgnoreCase))
? args.WebSession.Response.ResponseHeaders[headerName]
? args.WebSession.Response.ResponseHeaders.Headers[headerName]
: null;
}
}
......@@ -84,9 +84,9 @@ namespace Titanium.Web.Proxy
string scheme = authSchemes.FirstOrDefault(x => authHeader.Value.Equals(x, StringComparison.OrdinalIgnoreCase));
//clear any existing headers to avoid confusing bad servers
if (args.WebSession.Request.NonUniqueRequestHeaders.ContainsKey("Authorization"))
if (args.WebSession.Request.RequestHeaders.NonUniqueHeaders.ContainsKey("Authorization"))
{
args.WebSession.Request.NonUniqueRequestHeaders.Remove("Authorization");
args.WebSession.Request.RequestHeaders.NonUniqueHeaders.Remove("Authorization");
}
//initial value will match exactly any of the schemes
......@@ -97,13 +97,13 @@ namespace Titanium.Web.Proxy
var auth = new HttpHeader("Authorization", string.Concat(scheme, clientToken));
//replace existing authorization header if any
if (args.WebSession.Request.RequestHeaders.ContainsKey("Authorization"))
if (args.WebSession.Request.RequestHeaders.Headers.ContainsKey("Authorization"))
{
args.WebSession.Request.RequestHeaders["Authorization"] = auth;
args.WebSession.Request.RequestHeaders.Headers["Authorization"] = auth;
}
else
{
args.WebSession.Request.RequestHeaders.Add("Authorization", auth);
args.WebSession.Request.RequestHeaders.Headers.Add("Authorization", auth);
}
//don't need to send body for Authorization request
......@@ -122,7 +122,7 @@ namespace Titanium.Web.Proxy
string clientToken = WinAuthHandler.GetFinalAuthToken(args.WebSession.Request.Host, serverToken, args.Id);
//there will be an existing header from initial client request
args.WebSession.Request.RequestHeaders["Authorization"] = new HttpHeader("Authorization", string.Concat(scheme, clientToken));
args.WebSession.Request.RequestHeaders.Headers["Authorization"] = new HttpHeader("Authorization", string.Concat(scheme, clientToken));
//send body for final auth request
if (args.WebSession.Request.HasBody)
......
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