Commit 55312330 authored by Hakan Arıcı's avatar Hakan Arıcı

* PAC/WAPD script handling implementation

* Various code refactorings
parent a9d1011b
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using Titanium.Web.Proxy.Exceptions; using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Decompression; using Titanium.Web.Proxy.Decompression;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Http.Responses; using Titanium.Web.Proxy.Http.Responses;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Network;
using System.Net; using System.Net;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
/// <summary> /// <summary>
/// Holds info related to a single proxy session (single request/response sequence) /// Holds info related to a single proxy session (single request/response sequence)
/// A proxy session is bounded to a single connection from client /// A proxy session is bounded to a single connection from client
/// A proxy session ends when client terminates connection to proxy /// A proxy session ends when client terminates connection to proxy
/// or when server terminates connection from proxy /// or when server terminates connection from proxy
/// </summary> /// </summary>
public class SessionEventArgs : EventArgs, IDisposable public class SessionEventArgs : EventArgs, IDisposable
{ {
/// <summary> /// <summary>
/// Size of Buffers used by this object /// Size of Buffers used by this object
/// </summary> /// </summary>
private readonly int bufferSize; private readonly int bufferSize;
/// <summary> /// <summary>
/// Holds a reference to proxy response handler method /// Holds a reference to proxy response handler method
/// </summary> /// </summary>
private readonly Func<SessionEventArgs, Task> httpResponseHandler; private readonly Func<SessionEventArgs, Task> httpResponseHandler;
/// <summary> /// <summary>
/// Holds a reference to client /// Holds a reference to client
/// </summary> /// </summary>
internal ProxyClient ProxyClient { get; set; } internal ProxyClient ProxyClient { get; set; }
//Should we send a rerequest //Should we send a rerequest
public bool ReRequest public bool ReRequest
{ {
get; get;
set; set;
} }
/// <summary> /// <summary>
/// Does this session uses SSL /// Does this session uses SSL
/// </summary> /// </summary>
public bool IsHttps => WebSession.Request.RequestUri.Scheme == Uri.UriSchemeHttps; public bool IsHttps => WebSession.Request.RequestUri.Scheme == Uri.UriSchemeHttps;
public IPEndPoint ClientEndPoint => (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint; public IPEndPoint ClientEndPoint => (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint;
/// <summary> /// <summary>
/// A web session corresponding to a single request/response sequence /// A web session corresponding to a single request/response sequence
/// within a proxy connection /// within a proxy connection
/// </summary> /// </summary>
public HttpWebClient WebSession { get; set; } public HttpWebClient WebSession { get; set; }
public ExternalProxy CustomUpStreamHttpProxyUsed { get; set; } public ExternalProxy CustomUpStreamHttpProxyUsed { get; set; }
public ExternalProxy CustomUpStreamHttpsProxyUsed { get; set; } public ExternalProxy CustomUpStreamHttpsProxyUsed { get; set; }
/// <summary>
/// <summary> /// Constructor to initialize the proxy
/// Constructor to initialize the proxy /// </summary>
/// </summary> internal SessionEventArgs(int bufferSize, Func<SessionEventArgs, Task> httpResponseHandler)
internal SessionEventArgs(int bufferSize, Func<SessionEventArgs, Task> httpResponseHandler) {
{ this.bufferSize = bufferSize;
this.bufferSize = bufferSize; this.httpResponseHandler = httpResponseHandler;
this.httpResponseHandler = httpResponseHandler;
ProxyClient = new ProxyClient();
ProxyClient = new ProxyClient(); WebSession = new HttpWebClient();
WebSession = new HttpWebClient(); }
}
/// <summary>
/// <summary> /// Read request body content as bytes[] for current session
/// Read request body content as bytes[] for current session /// </summary>
/// </summary> private async Task ReadRequestBody()
private async Task ReadRequestBody() {
{ //GET request don't have a request body to read
//GET request don't have a request body to read if ((WebSession.Request.Method.ToUpper() != "POST" && WebSession.Request.Method.ToUpper() != "PUT"))
if ((WebSession.Request.Method.ToUpper() != "POST" && WebSession.Request.Method.ToUpper() != "PUT")) {
{ throw new BodyNotFoundException("Request don't have a body." +
throw new BodyNotFoundException("Request don't have a body." + "Please verify that this request is a Http POST/PUT and request " +
"Please verify that this request is a Http POST/PUT and request " + "content length is greater than zero before accessing the body.");
"content length is greater than zero before accessing the body."); }
}
//Caching check
//Caching check if (WebSession.Request.RequestBody == null)
if (WebSession.Request.RequestBody == null) {
{
//If chunked then its easy just read the whole body with the content length mentioned in the request header
//If chunked then its easy just read the whole body with the content length mentioned in the request header using (var requestBodyStream = new MemoryStream())
using (var requestBodyStream = new MemoryStream()) {
{ //For chunked request we need to read data as they arrive, until we reach a chunk end symbol
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol if (WebSession.Request.IsChunked)
if (WebSession.Request.IsChunked) {
{ await this.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(bufferSize, requestBodyStream);
await this.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(bufferSize, requestBodyStream); }
} else
else {
{ //If not chunked then its easy just read the whole body with the content length mentioned in the request header
//If not chunked then its easy just read the whole body with the content length mentioned in the request header if (WebSession.Request.ContentLength > 0)
if (WebSession.Request.ContentLength > 0) {
{ //If not chunked then its easy just read the amount of bytes mentioned in content length header of response
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response await this.ProxyClient.ClientStreamReader.CopyBytesToStream(bufferSize, requestBodyStream,
await this.ProxyClient.ClientStreamReader.CopyBytesToStream(bufferSize, requestBodyStream, WebSession.Request.ContentLength);
WebSession.Request.ContentLength);
}
} else if (WebSession.Request.HttpVersion.Major == 1 && WebSession.Request.HttpVersion.Minor == 0)
else if (WebSession.Request.HttpVersion.Major == 1 && WebSession.Request.HttpVersion.Minor == 0) {
{ await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, requestBodyStream, long.MaxValue);
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, requestBodyStream, long.MaxValue); }
} }
} WebSession.Request.RequestBody = await GetDecompressedResponseBody(WebSession.Request.ContentEncoding,
WebSession.Request.RequestBody = await GetDecompressedResponseBody(WebSession.Request.ContentEncoding, requestBodyStream.ToArray());
requestBodyStream.ToArray()); }
}
//Now set the flag to true
//Now set the flag to true //So that next time we can deliver body from cache
//So that next time we can deliver body from cache WebSession.Request.RequestBodyRead = true;
WebSession.Request.RequestBodyRead = true; }
}
}
}
/// <summary>
/// <summary> /// Read response body as byte[] for current response
/// Read response body as byte[] for current response /// </summary>
/// </summary> private async Task ReadResponseBody()
private async Task ReadResponseBody() {
{ //If not already read (not cached yet)
//If not already read (not cached yet) if (WebSession.Response.ResponseBody == null)
if (WebSession.Response.ResponseBody == null) {
{ using (var responseBodyStream = new MemoryStream())
using (var responseBodyStream = new MemoryStream()) {
{ //If chuncked the read chunk by chunk until we hit chunk end symbol
//If chuncked the read chunk by chunk until we hit chunk end symbol if (WebSession.Response.IsChunked)
if (WebSession.Response.IsChunked) {
{ await WebSession.ServerConnection.StreamReader.CopyBytesToStreamChunked(bufferSize, responseBodyStream);
await WebSession.ServerConnection.StreamReader.CopyBytesToStreamChunked(bufferSize, responseBodyStream); }
} else
else {
{ if (WebSession.Response.ContentLength > 0)
if (WebSession.Response.ContentLength > 0) {
{ //If not chunked then its easy just read the amount of bytes mentioned in content length header of response
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream,
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream, WebSession.Response.ContentLength);
WebSession.Response.ContentLength);
}
} else if ((WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0) || WebSession.Response.ContentLength == -1)
else if ((WebSession.Response.HttpVersion.Major == 1 && WebSession.Response.HttpVersion.Minor == 0) || WebSession.Response.ContentLength == -1) {
{ await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream, long.MaxValue);
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, responseBodyStream, long.MaxValue); }
} }
}
WebSession.Response.ResponseBody = await GetDecompressedResponseBody(WebSession.Response.ContentEncoding,
WebSession.Response.ResponseBody = await GetDecompressedResponseBody(WebSession.Response.ContentEncoding, responseBodyStream.ToArray());
responseBodyStream.ToArray());
}
} //set this to true for caching
//set this to true for caching WebSession.Response.ResponseBodyRead = true;
WebSession.Response.ResponseBodyRead = true; }
} }
}
/// <summary>
/// <summary> /// Gets the request body as bytes
/// Gets the request body as bytes /// </summary>
/// </summary> /// <returns></returns>
/// <returns></returns> public async Task<byte[]> GetRequestBody()
public async Task<byte[]> GetRequestBody() {
{ if (!WebSession.Request.RequestBodyRead)
if (!WebSession.Request.RequestBodyRead) {
{ if (WebSession.Request.RequestLocked)
if (WebSession.Request.RequestLocked) {
{ throw new Exception("You cannot call this function after request is made to server.");
throw new Exception("You cannot call this function after request is made to server."); }
}
await ReadRequestBody();
await ReadRequestBody(); }
} return WebSession.Request.RequestBody;
return WebSession.Request.RequestBody; }
} /// <summary>
/// <summary> /// Gets the request body as string
/// Gets the request body as string /// </summary>
/// </summary> /// <returns></returns>
/// <returns></returns> public async Task<string> GetRequestBodyAsString()
public async Task<string> GetRequestBodyAsString() {
{ if (!WebSession.Request.RequestBodyRead)
if (!WebSession.Request.RequestBodyRead) {
{ if (WebSession.Request.RequestLocked)
if (WebSession.Request.RequestLocked) {
{ throw new Exception("You cannot call this function after request is made to server.");
throw new Exception("You cannot call this function after request is made to server."); }
}
await ReadRequestBody();
await ReadRequestBody(); }
} //Use the encoding specified in request to decode the byte[] data to string
//Use the encoding specified in request to decode the byte[] data to string return WebSession.Request.RequestBodyString ?? (WebSession.Request.RequestBodyString = WebSession.Request.Encoding.GetString(WebSession.Request.RequestBody));
return WebSession.Request.RequestBodyString ?? (WebSession.Request.RequestBodyString = WebSession.Request.Encoding.GetString(WebSession.Request.RequestBody)); }
}
/// <summary>
/// <summary> /// Sets the request body
/// Sets the request body /// </summary>
/// </summary> /// <param name="body"></param>
/// <param name="body"></param> public async Task SetRequestBody(byte[] body)
public async Task SetRequestBody(byte[] body) {
{ if (WebSession.Request.RequestLocked)
if (WebSession.Request.RequestLocked) {
{ throw new Exception("You cannot call this function after request is made to server.");
throw new Exception("You cannot call this function after request is made to server."); }
}
//syphon out the request body from client before setting the new body
//syphon out the request body from client before setting the new body if (!WebSession.Request.RequestBodyRead)
if (!WebSession.Request.RequestBodyRead) {
{ await ReadRequestBody();
await ReadRequestBody(); }
}
WebSession.Request.RequestBody = body;
WebSession.Request.RequestBody = body;
if (WebSession.Request.IsChunked == false)
if (WebSession.Request.IsChunked == false) {
{ WebSession.Request.ContentLength = body.Length;
WebSession.Request.ContentLength = body.Length; }
} else
else {
{ WebSession.Request.ContentLength = -1;
WebSession.Request.ContentLength = -1; }
} }
}
/// <summary>
/// <summary> /// Sets the body with the specified string
/// Sets the body with the specified string /// </summary>
/// </summary> /// <param name="body"></param>
/// <param name="body"></param> public async Task SetRequestBodyString(string body)
public async Task SetRequestBodyString(string body) {
{ if (WebSession.Request.RequestLocked)
if (WebSession.Request.RequestLocked) {
{ throw new Exception("You cannot call this function after request is made to server.");
throw new Exception("You cannot call this function after request is made to server."); }
}
//syphon out the request body from client before setting the new body
//syphon out the request body from client before setting the new body if (!WebSession.Request.RequestBodyRead)
if (!WebSession.Request.RequestBodyRead) {
{ await ReadRequestBody();
await ReadRequestBody(); }
}
await SetRequestBody(WebSession.Request.Encoding.GetBytes(body));
await SetRequestBody(WebSession.Request.Encoding.GetBytes(body));
}
}
/// <summary>
/// <summary> /// Gets the response body as byte array
/// Gets the response body as byte array /// </summary>
/// </summary> /// <returns></returns>
/// <returns></returns> public async Task<byte[]> GetResponseBody()
public async Task<byte[]> GetResponseBody() {
{ if (!WebSession.Request.RequestLocked)
if (!WebSession.Request.RequestLocked) {
{ throw new Exception("You cannot call this function before request is made to server.");
throw new Exception("You cannot call this function before request is made to server."); }
}
await ReadResponseBody();
await ReadResponseBody(); return WebSession.Response.ResponseBody;
return WebSession.Response.ResponseBody; }
}
/// <summary>
/// <summary> /// Gets the response body as string
/// Gets the response body as string /// </summary>
/// </summary> /// <returns></returns>
/// <returns></returns> public async Task<string> GetResponseBodyAsString()
public async Task<string> GetResponseBodyAsString() {
{ if (!WebSession.Request.RequestLocked)
if (!WebSession.Request.RequestLocked) {
{ throw new Exception("You cannot call this function before request is made to server.");
throw new Exception("You cannot call this function before request is made to server."); }
}
await GetResponseBody();
await GetResponseBody();
return WebSession.Response.ResponseBodyString ??
return WebSession.Response.ResponseBodyString ?? (WebSession.Response.ResponseBodyString = WebSession.Response.Encoding.GetString(WebSession.Response.ResponseBody));
(WebSession.Response.ResponseBodyString = WebSession.Response.Encoding.GetString(WebSession.Response.ResponseBody)); }
}
/// <summary>
/// <summary> /// Set the response body bytes
/// Set the response body bytes /// </summary>
/// </summary> /// <param name="body"></param>
/// <param name="body"></param> public async Task SetResponseBody(byte[] body)
public async Task SetResponseBody(byte[] body) {
{ if (!WebSession.Request.RequestLocked)
if (!WebSession.Request.RequestLocked) {
{ throw new Exception("You cannot call this function before request is made to server.");
throw new Exception("You cannot call this function before request is made to server."); }
}
//syphon out the response body from server before setting the new body
//syphon out the response body from server before setting the new body if (WebSession.Response.ResponseBody == null)
if (WebSession.Response.ResponseBody == null) {
{ await GetResponseBody();
await GetResponseBody(); }
}
WebSession.Response.ResponseBody = body;
WebSession.Response.ResponseBody = body;
//If there is a content length header update it
//If there is a content length header update it if (WebSession.Response.IsChunked == false)
if (WebSession.Response.IsChunked == false) {
{ WebSession.Response.ContentLength = body.Length;
WebSession.Response.ContentLength = body.Length; }
} else
else {
{ WebSession.Response.ContentLength = -1;
WebSession.Response.ContentLength = -1; }
} }
}
/// <summary>
/// <summary> /// Replace the response body with the specified string
/// Replace the response body with the specified string /// </summary>
/// </summary> /// <param name="body"></param>
/// <param name="body"></param> public async Task SetResponseBodyString(string body)
public async Task SetResponseBodyString(string body) {
{ if (!WebSession.Request.RequestLocked)
if (!WebSession.Request.RequestLocked) {
{ throw new Exception("You cannot call this function before request is made to server.");
throw new Exception("You cannot call this function before request is made to server."); }
}
//syphon out the response body from server before setting the new body
//syphon out the response body from server before setting the new body if (WebSession.Response.ResponseBody == null)
if (WebSession.Response.ResponseBody == null) {
{ await GetResponseBody();
await GetResponseBody(); }
}
var bodyBytes = WebSession.Response.Encoding.GetBytes(body);
var bodyBytes = WebSession.Response.Encoding.GetBytes(body);
await SetResponseBody(bodyBytes);
await SetResponseBody(bodyBytes); }
}
private async Task<byte[]> GetDecompressedResponseBody(string encodingType, byte[] responseBodyStream)
private async Task<byte[]> GetDecompressedResponseBody(string encodingType, byte[] responseBodyStream) {
{ var decompressionFactory = new DecompressionFactory();
var decompressionFactory = new DecompressionFactory(); var decompressor = decompressionFactory.Create(encodingType);
var decompressor = decompressionFactory.Create(encodingType);
return await decompressor.Decompress(responseBodyStream, bufferSize);
return await decompressor.Decompress(responseBodyStream, bufferSize); }
}
/// <summary>
/// <summary> /// Before request is made to server
/// Before request is made to server /// Respond with the specified HTML string to client
/// Respond with the specified HTML string to client /// and ignore the request
/// and ignore the request /// </summary>
/// </summary> /// <param name="html"></param>
/// <param name="html"></param> public async Task Ok(string html)
public async Task Ok(string html) {
{ await Ok(html, null);
await Ok(html, null); }
}
/// <summary>
/// <summary> /// Before request is made to server
/// Before request is made to server /// Respond with the specified HTML string to client
/// Respond with the specified HTML string to client /// and ignore the request
/// and ignore the request /// </summary>
/// </summary> /// <param name="html"></param>
/// <param name="html"></param> /// <param name="headers"></param>
/// <param name="headers"></param> public async Task Ok(string html, Dictionary<string, HttpHeader> headers)
public async Task Ok(string html, Dictionary<string, HttpHeader> headers) {
{ if (WebSession.Request.RequestLocked)
if (WebSession.Request.RequestLocked) {
{ throw new Exception("You cannot call this function after request is made to server.");
throw new Exception("You cannot call this function after request is made to server."); }
}
if (html == null)
if (html == null) {
{ html = string.Empty;
html = string.Empty; }
}
var result = Encoding.Default.GetBytes(html);
var result = Encoding.Default.GetBytes(html);
await Ok(result, headers);
await Ok(result, headers); }
}
/// <summary>
/// <summary> /// Before request is made to server
/// Before request is made to server /// Respond with the specified byte[] to client
/// Respond with the specified byte[] to client /// and ignore the request
/// and ignore the request /// </summary>
/// </summary> /// <param name="result"></param>
/// <param name="result"></param> public async Task Ok(byte[] result)
public async Task Ok(byte[] result) {
{ await Ok(result, null);
await Ok(result, null); }
}
/// <summary>
/// <summary> /// Before request is made to server
/// Before request is made to server /// Respond with the specified byte[] to client
/// Respond with the specified byte[] to client /// and ignore the request
/// and ignore the request /// </summary>
/// </summary> /// <param name="result"></param>
/// <param name="result"></param> /// <param name="headers"></param>
/// <param name="headers"></param> public async Task Ok(byte[] result, Dictionary<string, HttpHeader> headers)
public async Task Ok(byte[] result, Dictionary<string, HttpHeader> headers) {
{ var response = new OkResponse();
var response = new OkResponse(); if (headers != null && headers.Count > 0)
if (headers != null && headers.Count > 0) {
{ response.ResponseHeaders = headers;
response.ResponseHeaders = headers; }
} response.HttpVersion = WebSession.Request.HttpVersion;
response.HttpVersion = WebSession.Request.HttpVersion; response.ResponseBody = result;
response.ResponseBody = result;
await Respond(response);
await Respond(response);
WebSession.Request.CancelRequest = true;
WebSession.Request.CancelRequest = true; }
}
public async Task Redirect(string url)
public async Task Redirect(string url) {
{ var response = new RedirectResponse();
var response = new RedirectResponse();
response.HttpVersion = WebSession.Request.HttpVersion;
response.HttpVersion = WebSession.Request.HttpVersion; response.ResponseHeaders.Add("Location", new Models.HttpHeader("Location", url));
response.ResponseHeaders.Add("Location", new Models.HttpHeader("Location", url)); response.ResponseBody = Encoding.ASCII.GetBytes(string.Empty);
response.ResponseBody = Encoding.ASCII.GetBytes(string.Empty);
await Respond(response);
await Respond(response);
WebSession.Request.CancelRequest = true;
WebSession.Request.CancelRequest = true; }
}
/// a generic responder method
/// a generic responder method public async Task Respond(Response response)
public async Task Respond(Response response) {
{ WebSession.Request.RequestLocked = true;
WebSession.Request.RequestLocked = true;
response.ResponseLocked = true;
response.ResponseLocked = true; response.ResponseBodyRead = true;
response.ResponseBodyRead = true;
WebSession.Response = response;
WebSession.Response = response;
await httpResponseHandler(this);
await httpResponseHandler(this); }
}
/// <summary>
/// <summary> /// implement any cleanup here
/// implement any cleanup here /// </summary>
/// </summary> public void Dispose()
public void Dispose() {
{
}
} }
}
} }
\ No newline at end of file
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Net.Security; using System.Net.Security;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Authentication; using System.Security.Authentication;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Tcp; using Titanium.Web.Proxy.Tcp;
namespace Titanium.Web.Proxy.Helpers namespace Titanium.Web.Proxy.Helpers
{ {
internal enum IpVersion internal enum IpVersion
{ {
Ipv4 = 1, Ipv4 = 1,
Ipv6 = 2, Ipv6 = 2,
} }
internal partial class NativeMethods internal partial class NativeMethods
{ {
internal const int AfInet = 2; internal const int AfInet = 2;
internal const int AfInet6 = 23; internal const int AfInet6 = 23;
internal enum TcpTableType internal enum TcpTableType
{ {
BasicListener, BasicListener,
BasicConnections, BasicConnections,
BasicAll, BasicAll,
OwnerPidListener, OwnerPidListener,
OwnerPidConnections, OwnerPidConnections,
OwnerPidAll, OwnerPidAll,
OwnerModuleListener, OwnerModuleListener,
OwnerModuleConnections, OwnerModuleConnections,
OwnerModuleAll, OwnerModuleAll,
} }
/// <summary> /// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366921.aspx"/> /// <see cref="http://msdn2.microsoft.com/en-us/library/aa366921.aspx"/>
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal struct TcpTable internal struct TcpTable
{ {
public uint length; public uint length;
public TcpRow row; public TcpRow row;
} }
/// <summary> /// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/> /// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal struct TcpRow internal struct TcpRow
{ {
public TcpState state; public TcpState state;
public uint localAddr; public uint localAddr;
public byte localPort1; public byte localPort1;
public byte localPort2; public byte localPort2;
public byte localPort3; public byte localPort3;
public byte localPort4; public byte localPort4;
public uint remoteAddr; public uint remoteAddr;
public byte remotePort1; public byte remotePort1;
public byte remotePort2; public byte remotePort2;
public byte remotePort3; public byte remotePort3;
public byte remotePort4; public byte remotePort4;
public int owningPid; public int owningPid;
} }
/// <summary> /// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/> /// <see cref="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/>
/// </summary> /// </summary>
[DllImport("iphlpapi.dll", SetLastError = true)] [DllImport("iphlpapi.dll", SetLastError = true)]
internal static extern uint GetExtendedTcpTable(IntPtr tcpTable, ref int size, bool sort, int ipVersion, int tableClass, int reserved); internal static extern uint GetExtendedTcpTable(IntPtr tcpTable, ref int size, bool sort, int ipVersion, int tableClass, int reserved);
} }
internal class TcpHelper internal class TcpHelper
{ {
/// <summary> /// <summary>
/// Gets the extended TCP table. /// Gets the extended TCP table.
/// </summary> /// </summary>
/// <returns>Collection of <see cref="TcpRow"/>.</returns> /// <returns>Collection of <see cref="TcpRow"/>.</returns>
internal static TcpTable GetExtendedTcpTable(IpVersion ipVersion) internal static TcpTable GetExtendedTcpTable(IpVersion ipVersion)
{ {
List<TcpRow> tcpRows = new List<TcpRow>(); List<TcpRow> tcpRows = new List<TcpRow>();
IntPtr tcpTable = IntPtr.Zero; IntPtr tcpTable = IntPtr.Zero;
int tcpTableLength = 0; int tcpTableLength = 0;
var ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6; 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 try
{ {
tcpTable = Marshal.AllocHGlobal(tcpTableLength); 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) for (int i = 0; i < table.length; ++i)
{ {
tcpRows.Add(new TcpRow((NativeMethods.TcpRow)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow)))); tcpRows.Add(new TcpRow((NativeMethods.TcpRow)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow))));
rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow))); rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow)));
} }
} }
} }
finally finally
{ {
if (tcpTable != IntPtr.Zero) if (tcpTable != IntPtr.Zero)
{ {
Marshal.FreeHGlobal(tcpTable); Marshal.FreeHGlobal(tcpTable);
} }
} }
} }
return new TcpTable(tcpRows); return new TcpTable(tcpRows);
} }
/// <summary> /// <summary>
/// relays the input clientStream to the server at the specified host name & port with the given httpCmd & headers as prefix /// relays the input clientStream to the server at the specified host name & port with the given httpCmd & headers as prefix
/// Usefull for websocket requests /// Usefull for websocket requests
/// </summary> /// </summary>
/// <param name="bufferSize"></param> /// <param name="bufferSize"></param>
/// <param name="connectionTimeOutSeconds"></param> /// <param name="connectionTimeOutSeconds"></param>
/// <param name="remoteHostName"></param> /// <param name="remoteHostName"></param>
/// <param name="httpCmd"></param> /// <param name="httpCmd"></param>
/// <param name="httpVersion"></param> /// <param name="httpVersion"></param>
/// <param name="requestHeaders"></param> /// <param name="requestHeaders"></param>
/// <param name="isHttps"></param> /// <param name="isHttps"></param>
/// <param name="remotePort"></param> /// <param name="remotePort"></param>
/// <param name="supportedProtocols"></param> /// <param name="supportedProtocols"></param>
/// <param name="remoteCertificateValidationCallback"></param> /// <param name="remoteCertificateValidationCallback"></param>
/// <param name="localCertificateSelectionCallback"></param> /// <param name="localCertificateSelectionCallback"></param>
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <param name="tcpConnectionFactory"></param> /// <param name="tcpConnectionFactory"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task SendRaw(int bufferSize, int connectionTimeOutSeconds, internal static async Task SendRaw(int bufferSize, int connectionTimeOutSeconds,
string remoteHostName, int remotePort, string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders, string remoteHostName, int remotePort, string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders,
bool isHttps, SslProtocols supportedProtocols, bool isHttps, SslProtocols supportedProtocols,
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback, RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
Stream clientStream, TcpConnectionFactory tcpConnectionFactory) Stream clientStream, TcpConnectionFactory tcpConnectionFactory)
{ {
//prepare the prefix content //prepare the prefix content
StringBuilder sb = null; StringBuilder sb = null;
if (httpCmd != null || requestHeaders != null) if (httpCmd != null || requestHeaders != null)
{ {
sb = new StringBuilder(); sb = new StringBuilder();
if (httpCmd != null) if (httpCmd != null)
{ {
sb.Append(httpCmd); sb.Append(httpCmd);
sb.Append(Environment.NewLine); sb.Append(Environment.NewLine);
} }
if (requestHeaders != null) if (requestHeaders != null)
{ {
foreach (var header in requestHeaders.Select(t => t.Value.ToString())) foreach (var header in requestHeaders.Select(t => t.Value.ToString()))
{ {
sb.Append(header); sb.Append(header);
sb.Append(Environment.NewLine); sb.Append(Environment.NewLine);
} }
} }
sb.Append(Environment.NewLine); sb.Append(Environment.NewLine);
} }
var tcpConnection = await tcpConnectionFactory.CreateClient(bufferSize, connectionTimeOutSeconds, var tcpConnection = await tcpConnectionFactory.CreateClient(bufferSize, connectionTimeOutSeconds,
remoteHostName, remotePort, remoteHostName, remotePort,
httpVersion, isHttps, httpVersion, isHttps,
supportedProtocols, remoteCertificateValidationCallback, localCertificateSelectionCallback, supportedProtocols, remoteCertificateValidationCallback, localCertificateSelectionCallback,
null, null, clientStream); null, null, clientStream);
try try
{ {
Stream tunnelStream = tcpConnection.Stream; Stream tunnelStream = tcpConnection.Stream;
Task sendRelay; //Now async relay all server=>client & client=>server data
var sendRelay = clientStream.CopyToAsync(sb?.ToString() ?? string.Empty, tunnelStream);
//Now async relay all server=>client & client=>server data
sendRelay = clientStream.CopyToAsync(sb?.ToString() ?? string.Empty, tunnelStream); var receiveRelay = tunnelStream.CopyToAsync(string.Empty, clientStream);
await Task.WhenAll(sendRelay, receiveRelay);
var receiveRelay = tunnelStream.CopyToAsync(string.Empty, clientStream); }
finally
await Task.WhenAll(sendRelay, receiveRelay); {
} tcpConnection.Dispose();
finally }
{ }
tcpConnection.Dispose(); }
}
}
}
} }
\ No newline at end of file
...@@ -16,7 +16,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -16,7 +16,7 @@ namespace Titanium.Web.Proxy.Network
internal ExternalProxy UpStreamHttpsProxy { get; set; } internal ExternalProxy UpStreamHttpsProxy { get; set; }
internal string HostName { get; set; } internal string HostName { get; set; }
internal int port { get; set; } internal int Port { get; set; }
internal bool IsHttps { get; set; } internal bool IsHttps { get; set; }
......
...@@ -49,7 +49,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -49,7 +49,7 @@ namespace Titanium.Web.Proxy.Network
SslStream sslStream = null; SslStream sslStream = null;
//If this proxy uses another external proxy then create a tunnel request for HTTPS connections //If this proxy uses another external proxy then create a tunnel request for HTTPS connections
if (externalHttpsProxy != null) if (externalHttpsProxy != null && externalHttpsProxy.HostName != remoteHostName)
{ {
client = new TcpClient(externalHttpsProxy.HostName, externalHttpsProxy.Port); client = new TcpClient(externalHttpsProxy.HostName, externalHttpsProxy.Port);
stream = client.GetStream(); stream = client.GetStream();
...@@ -100,17 +100,14 @@ namespace Titanium.Web.Proxy.Network ...@@ -100,17 +100,14 @@ namespace Titanium.Web.Proxy.Network
} }
catch catch
{ {
if (sslStream != null) sslStream?.Dispose();
{
sslStream.Dispose();
}
throw; throw;
} }
} }
else else
{ {
if (externalHttpProxy != null) if (externalHttpProxy != null && externalHttpProxy.HostName != remoteHostName)
{ {
client = new TcpClient(externalHttpProxy.HostName, externalHttpProxy.Port); client = new TcpClient(externalHttpProxy.HostName, externalHttpProxy.Port);
stream = client.GetStream(); stream = client.GetStream();
...@@ -133,7 +130,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -133,7 +130,7 @@ namespace Titanium.Web.Proxy.Network
UpStreamHttpProxy = externalHttpProxy, UpStreamHttpProxy = externalHttpProxy,
UpStreamHttpsProxy = externalHttpsProxy, UpStreamHttpsProxy = externalHttpsProxy,
HostName = remoteHostName, HostName = remoteHostName,
port = remotePort, Port = remotePort,
IsHttps = isHttps, IsHttps = isHttps,
TcpClient = client, TcpClient = client,
StreamReader = new CustomBinaryReader(stream), StreamReader = new CustomBinaryReader(stream),
......
...@@ -186,6 +186,11 @@ namespace Titanium.Web.Proxy ...@@ -186,6 +186,11 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public bool ProxyRunning => proxyRunning; public bool ProxyRunning => proxyRunning;
/// <summary>
/// Gets or sets a value indicating whether requests will be chained to upstream gateway.
/// </summary>
public bool ForwardToUpstreamGateway { get; set; }
/// <summary> /// <summary>
/// Constructor /// Constructor
/// </summary> /// </summary>
...@@ -348,6 +353,12 @@ namespace Titanium.Web.Proxy ...@@ -348,6 +353,12 @@ namespace Titanium.Web.Proxy
certificateCacheManager.TrustRootCertificate(); certificateCacheManager.TrustRootCertificate();
} }
if (ForwardToUpstreamGateway && GetCustomUpStreamHttpProxyFunc == null && GetCustomUpStreamHttpsProxyFunc == null)
{
GetCustomUpStreamHttpProxyFunc = GetSystemUpStreamProxy;
GetCustomUpStreamHttpsProxyFunc = GetSystemUpStreamProxy;
}
foreach (var endPoint in ProxyEndPoints) foreach (var endPoint in ProxyEndPoints)
{ {
Listen(endPoint); Listen(endPoint);
...@@ -358,7 +369,29 @@ namespace Titanium.Web.Proxy ...@@ -358,7 +369,29 @@ namespace Titanium.Web.Proxy
proxyRunning = true; proxyRunning = true;
} }
/// <summary> /// <summary>
/// Gets the system up stream proxy.
/// </summary>
/// <param name="sessionEventArgs">The <see cref="SessionEventArgs"/> instance containing the event data.</param>
/// <returns><see cref="ExternalProxy"/> instance containing valid proxy configuration from PAC/WAPD scripts if any exists.</returns>
private Task<ExternalProxy> GetSystemUpStreamProxy(SessionEventArgs sessionEventArgs)
{
// Use built-in WebProxy class to handle PAC/WAPD scripts.
var systemProxyResolver = new WebProxy();
var systemProxyUri = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri);
// TODO: Apply authorization
var systemProxy = new ExternalProxy
{
HostName = systemProxyUri.Host,
Port = systemProxyUri.Port
};
return Task.FromResult(systemProxy);
}
/// <summary>
/// Stop this proxy server /// Stop this proxy server
/// </summary> /// </summary>
public void Stop() public void Stop()
......
...@@ -165,7 +165,6 @@ namespace Titanium.Web.Proxy ...@@ -165,7 +165,6 @@ namespace Titanium.Web.Proxy
//write back successfull CONNECT response //write back successfull CONNECT response
await WriteConnectResponse(clientStreamWriter, version); await WriteConnectResponse(clientStreamWriter, version);
await TcpHelper.SendRaw(BUFFER_SIZE, ConnectionTimeOutSeconds, httpRemoteUri.Host, httpRemoteUri.Port, await TcpHelper.SendRaw(BUFFER_SIZE, ConnectionTimeOutSeconds, httpRemoteUri.Host, httpRemoteUri.Port,
httpCmd, version, null, httpCmd, version, null,
false, SupportedSslProtocols, false, SupportedSslProtocols,
...@@ -274,10 +273,8 @@ namespace Titanium.Web.Proxy ...@@ -274,10 +273,8 @@ namespace Titanium.Web.Proxy
customUpStreamHttpProxy ?? UpStreamHttpProxy, customUpStreamHttpsProxy ?? UpStreamHttpsProxy, args.ProxyClient.ClientStream); customUpStreamHttpProxy ?? UpStreamHttpProxy, customUpStreamHttpsProxy ?? UpStreamHttpsProxy, args.ProxyClient.ClientStream);
} }
args.WebSession.Request.RequestLocked = true; args.WebSession.Request.RequestLocked = true;
//If request was cancelled by user then dispose the client //If request was cancelled by user then dispose the client
if (args.WebSession.Request.CancelRequest) if (args.WebSession.Request.CancelRequest)
{ {
...@@ -362,10 +359,10 @@ namespace Titanium.Web.Proxy ...@@ -362,10 +359,10 @@ namespace Titanium.Web.Proxy
return; return;
} }
if (CloseConnection && connection != null) if (CloseConnection)
{ {
//dispose //dispose
connection.Dispose(); connection?.Dispose();
} }
} }
...@@ -472,7 +469,6 @@ namespace Titanium.Web.Proxy ...@@ -472,7 +469,6 @@ namespace Titanium.Web.Proxy
break; break;
} }
PrepareRequestHeaders(args.WebSession.Request.RequestHeaders, args.WebSession); PrepareRequestHeaders(args.WebSession.Request.RequestHeaders, args.WebSession);
args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority; args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority;
......
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