Commit 1acc1ad8 authored by Jehonathan's avatar Jehonathan Committed by GitHub

Merge pull request #138 from aricih/release

ProcessId is fixed on HttpWebClient
parents 13bd6bc0 78aa6639
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 partial class NativeMethods internal enum IpVersion
{ {
internal const int AfInet = 2; Ipv4 = 1,
Ipv6 = 2,
internal enum TcpTableType }
{
BasicListener, internal partial class NativeMethods
BasicConnections, {
BasicAll, internal const int AfInet = 2;
OwnerPidListener, internal const int AfInet6 = 23;
OwnerPidConnections,
OwnerPidAll, internal enum TcpTableType
OwnerModuleListener, {
OwnerModuleConnections, BasicListener,
OwnerModuleAll, BasicConnections,
} BasicAll,
OwnerPidListener,
/// <summary> OwnerPidConnections,
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366921.aspx"/> OwnerPidAll,
/// </summary> OwnerModuleListener,
[StructLayout(LayoutKind.Sequential)] OwnerModuleConnections,
internal struct TcpTable OwnerModuleAll,
{ }
public uint length;
public TcpRow row; /// <summary>
} /// <see cref="http://msdn2.microsoft.com/en-us/library/aa366921.aspx"/>
/// </summary>
/// <summary> [StructLayout(LayoutKind.Sequential)]
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/> internal struct TcpTable
/// </summary> {
[StructLayout(LayoutKind.Sequential)] public uint length;
internal struct TcpRow public TcpRow row;
{ }
public TcpState state;
public uint localAddr; /// <summary>
public byte localPort1; /// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
public byte localPort2; /// </summary>
public byte localPort3; [StructLayout(LayoutKind.Sequential)]
public byte localPort4; internal struct TcpRow
public uint remoteAddr; {
public byte remotePort1; public TcpState state;
public byte remotePort2; public uint localAddr;
public byte remotePort3; public byte localPort1;
public byte remotePort4; public byte localPort2;
public int owningPid; public byte localPort3;
} public byte localPort4;
public uint remoteAddr;
/// <summary> public byte remotePort1;
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/> public byte remotePort2;
/// </summary> public byte remotePort3;
[DllImport("iphlpapi.dll", SetLastError = true)] public byte remotePort4;
internal static extern uint GetExtendedTcpTable(IntPtr tcpTable, ref int size, bool sort, int ipVersion, int tableClass, int reserved); public int owningPid;
} }
internal class TcpHelper /// <summary>
{ /// <see cref="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/>
/// <summary> /// </summary>
/// Gets the extended TCP table. [DllImport("iphlpapi.dll", SetLastError = true)]
/// </summary> internal static extern uint GetExtendedTcpTable(IntPtr tcpTable, ref int size, bool sort, int ipVersion, int tableClass, int reserved);
/// <returns>Collection of <see cref="TcpRow"/>.</returns> }
internal static TcpTable GetExtendedTcpTable()
{ internal class TcpHelper
List<TcpRow> tcpRows = new List<TcpRow>(); {
/// <summary>
IntPtr tcpTable = IntPtr.Zero; /// Gets the extended TCP table.
int tcpTableLength = 0; /// </summary>
/// <returns>Collection of <see cref="TcpRow"/>.</returns>
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, NativeMethods.AfInet, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) != 0) internal static TcpTable GetExtendedTcpTable(IpVersion ipVersion)
{ {
try List<TcpRow> tcpRows = new List<TcpRow>();
{
tcpTable = Marshal.AllocHGlobal(tcpTableLength); IntPtr tcpTable = IntPtr.Zero;
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, NativeMethods.AfInet, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) == 0) int tcpTableLength = 0;
{
NativeMethods.TcpTable table = (NativeMethods.TcpTable)Marshal.PtrToStructure(tcpTable, typeof(NativeMethods.TcpTable)); var ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6;
IntPtr rowPtr = (IntPtr)((long)tcpTable + Marshal.SizeOf(table.length)); if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) != 0)
{
for (int i = 0; i < table.length; ++i) try
{ {
tcpRows.Add(new TcpRow((NativeMethods.TcpRow)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow)))); tcpTable = Marshal.AllocHGlobal(tcpTableLength);
rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow))); 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));
}
finally IntPtr rowPtr = (IntPtr)((long)tcpTable + Marshal.SizeOf(table.length));
{
if (tcpTable != IntPtr.Zero) for (int i = 0; i < table.length; ++i)
{ {
Marshal.FreeHGlobal(tcpTable); tcpRows.Add(new TcpRow((NativeMethods.TcpRow)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow))));
} rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow)));
} }
} }
}
return new TcpTable(tcpRows); finally
} {
if (tcpTable != IntPtr.Zero)
/// <summary> {
/// relays the input clientStream to the server at the specified host name & port with the given httpCmd & headers as prefix Marshal.FreeHGlobal(tcpTable);
/// Usefull for websocket requests }
/// </summary> }
/// <param name="bufferSize"></param> }
/// <param name="connectionTimeOutSeconds"></param>
/// <param name="remoteHostName"></param> return new TcpTable(tcpRows);
/// <param name="httpCmd"></param> }
/// <param name="httpVersion"></param>
/// <param name="requestHeaders"></param> /// <summary>
/// <param name="isHttps"></param> /// relays the input clientStream to the server at the specified host name & port with the given httpCmd & headers as prefix
/// <param name="remotePort"></param> /// Usefull for websocket requests
/// <param name="supportedProtocols"></param> /// </summary>
/// <param name="remoteCertificateValidationCallback"></param> /// <param name="bufferSize"></param>
/// <param name="localCertificateSelectionCallback"></param> /// <param name="connectionTimeOutSeconds"></param>
/// <param name="clientStream"></param> /// <param name="remoteHostName"></param>
/// <param name="tcpConnectionFactory"></param> /// <param name="httpCmd"></param>
/// <returns></returns> /// <param name="httpVersion"></param>
internal static async Task SendRaw(int bufferSize, int connectionTimeOutSeconds, /// <param name="requestHeaders"></param>
string remoteHostName, int remotePort, string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders, /// <param name="isHttps"></param>
bool isHttps, SslProtocols supportedProtocols, /// <param name="remotePort"></param>
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback, /// <param name="supportedProtocols"></param>
Stream clientStream, TcpConnectionFactory tcpConnectionFactory) /// <param name="remoteCertificateValidationCallback"></param>
{ /// <param name="localCertificateSelectionCallback"></param>
//prepare the prefix content /// <param name="clientStream"></param>
StringBuilder sb = null; /// <param name="tcpConnectionFactory"></param>
if (httpCmd != null || requestHeaders != null) /// <returns></returns>
{ internal static async Task SendRaw(int bufferSize, int connectionTimeOutSeconds,
sb = new StringBuilder(); string remoteHostName, int remotePort, string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders,
bool isHttps, SslProtocols supportedProtocols,
if (httpCmd != null) RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
{ Stream clientStream, TcpConnectionFactory tcpConnectionFactory)
sb.Append(httpCmd); {
sb.Append(Environment.NewLine); //prepare the prefix content
} StringBuilder sb = null;
if (httpCmd != null || requestHeaders != null)
if (requestHeaders != null) {
{ sb = new StringBuilder();
foreach (var header in requestHeaders.Select(t => t.Value.ToString()))
{ if (httpCmd != null)
sb.Append(header); {
sb.Append(Environment.NewLine); sb.Append(httpCmd);
} sb.Append(Environment.NewLine);
} }
sb.Append(Environment.NewLine); if (requestHeaders != null)
} {
foreach (var header in requestHeaders.Select(t => t.Value.ToString()))
var tcpConnection = await tcpConnectionFactory.CreateClient(bufferSize, connectionTimeOutSeconds, {
remoteHostName, remotePort, sb.Append(header);
httpVersion, isHttps, sb.Append(Environment.NewLine);
supportedProtocols, remoteCertificateValidationCallback, localCertificateSelectionCallback, }
null, null, clientStream); }
try sb.Append(Environment.NewLine);
{ }
Stream tunnelStream = tcpConnection.Stream;
var tcpConnection = await tcpConnectionFactory.CreateClient(bufferSize, connectionTimeOutSeconds,
Task sendRelay; remoteHostName, remotePort,
httpVersion, isHttps,
//Now async relay all server=>client & client=>server data supportedProtocols, remoteCertificateValidationCallback, localCertificateSelectionCallback,
sendRelay = clientStream.CopyToAsync(sb?.ToString() ?? string.Empty, tunnelStream); null, null, clientStream);
try
var receiveRelay = tunnelStream.CopyToAsync(string.Empty, clientStream); {
Stream tunnelStream = tcpConnection.Stream;
await Task.WhenAll(sendRelay, receiveRelay);
} //Now async relay all server=>client & client=>server data
finally var sendRelay = clientStream.CopyToAsync(sb?.ToString() ?? string.Empty, tunnelStream);
{
tcpConnection.Dispose(); var receiveRelay = tunnelStream.CopyToAsync(string.Empty, clientStream);
}
} await Task.WhenAll(sendRelay, receiveRelay);
} }
finally
{
tcpConnection.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.Text;
using System.Text; using System.Threading.Tasks;
using System.Threading.Tasks; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Shared;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Shared; namespace Titanium.Web.Proxy.Http
using Titanium.Web.Proxy.Tcp; {
/// <summary>
namespace Titanium.Web.Proxy.Http /// Used to communicate with the server over HTTP(S)
{ /// </summary>
/// <summary> public class HttpWebClient
/// Used to communicate with the server over HTTP(S) {
/// </summary> private int processId;
public class HttpWebClient
{ /// <summary>
private int processId; /// Connection to server
/// </summary>
/// <summary> internal TcpConnection ServerConnection { get; set; }
/// Connection to server
/// </summary>
internal TcpConnection ServerConnection { get; set; } public List<HttpHeader> ConnectHeaders { get; set; }
public Request Request { get; set; }
public Response Response { get; set; }
public List<HttpHeader> ConnectHeaders { get; set; }
public Request Request { get; set; } /// <summary>
public Response Response { get; set; } /// PID of the process that is created the current session
/// </summary>
/// <summary> public int ProcessId { get; internal set; }
/// PID of the process that is created the current session
/// </summary> /// <summary>
public int ProcessId /// Is Https?
{ /// </summary>
get public bool IsHttps => this.Request.RequestUri.Scheme == Uri.UriSchemeHttps;
{
if (processId == 0) /// <summary>
{ /// Set the tcp connection to server used by this webclient
TcpRow tcpRow = TcpHelper.GetExtendedTcpTable().TcpRows /// </summary>
.FirstOrDefault(row => row.LocalEndPoint.Port == ServerConnection.port); /// <param name="connection">Instance of <see cref="TcpConnection"/></param>
internal void SetConnection(TcpConnection connection)
processId = tcpRow?.ProcessId ?? -1; {
} connection.LastAccess = DateTime.Now;
ServerConnection = connection;
return processId; }
}
} internal HttpWebClient()
{
/// <summary> this.Request = new Request();
/// Is Https? this.Response = new Response();
/// </summary> }
public bool IsHttps => this.Request.RequestUri.Scheme == Uri.UriSchemeHttps;
/// <summary>
/// <summary> /// Prepare & send the http(s) request
/// Set the tcp connection to server used by this webclient /// </summary>
/// </summary> /// <returns></returns>
/// <param name="connection">Instance of <see cref="TcpConnection"/></param> internal async Task SendRequest(bool enable100ContinueBehaviour)
internal void SetConnection(TcpConnection connection) {
{ Stream stream = ServerConnection.Stream;
connection.LastAccess = DateTime.Now;
ServerConnection = connection; StringBuilder requestLines = new StringBuilder();
}
//prepare the request & headers
internal HttpWebClient() if ((ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false) || (ServerConnection.UpStreamHttpsProxy != null && ServerConnection.IsHttps == true))
{ {
this.Request = new Request(); requestLines.AppendLine(string.Join(" ", this.Request.Method, this.Request.RequestUri.AbsoluteUri, $"HTTP/{this.Request.HttpVersion.Major}.{this.Request.HttpVersion.Minor}"));
this.Response = new Response(); }
} else
{
/// <summary> requestLines.AppendLine(string.Join(" ", this.Request.Method, this.Request.RequestUri.PathAndQuery, $"HTTP/{this.Request.HttpVersion.Major}.{this.Request.HttpVersion.Minor}"));
/// Prepare & send the http(s) request }
/// </summary>
/// <returns></returns> //Send Authentication to Upstream proxy if needed
internal async Task SendRequest(bool enable100ContinueBehaviour) if (ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false && !string.IsNullOrEmpty(ServerConnection.UpStreamHttpProxy.UserName) && ServerConnection.UpStreamHttpProxy.Password != null)
{ {
Stream stream = ServerConnection.Stream; requestLines.AppendLine("Proxy-Connection: keep-alive");
requestLines.AppendLine("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(ServerConnection.UpStreamHttpProxy.UserName + ":" + ServerConnection.UpStreamHttpProxy.Password)));
StringBuilder requestLines = new StringBuilder(); }
//write request headers
//prepare the request & headers foreach (var headerItem in this.Request.RequestHeaders)
if ((ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false) || (ServerConnection.UpStreamHttpsProxy != null && ServerConnection.IsHttps == true)) {
{ var header = headerItem.Value;
requestLines.AppendLine(string.Join(" ", this.Request.Method, this.Request.RequestUri.AbsoluteUri, $"HTTP/{this.Request.HttpVersion.Major}.{this.Request.HttpVersion.Minor}")); if (headerItem.Key != "Proxy-Authorization")
} {
else requestLines.AppendLine(header.Name + ':' + header.Value);
{ }
requestLines.AppendLine(string.Join(" ", this.Request.Method, this.Request.RequestUri.PathAndQuery, $"HTTP/{this.Request.HttpVersion.Major}.{this.Request.HttpVersion.Minor}")); }
}
//write non unique request headers
//Send Authentication to Upstream proxy if needed foreach (var headerItem in this.Request.NonUniqueRequestHeaders)
if (ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false && !string.IsNullOrEmpty(ServerConnection.UpStreamHttpProxy.UserName) && ServerConnection.UpStreamHttpProxy.Password != null) {
{ var headers = headerItem.Value;
requestLines.AppendLine("Proxy-Connection: keep-alive"); foreach (var header in headers)
requestLines.AppendLine("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(ServerConnection.UpStreamHttpProxy.UserName + ":" + ServerConnection.UpStreamHttpProxy.Password))); {
} if (headerItem.Key != "Proxy-Authorization")
//write request headers {
foreach (var headerItem in this.Request.RequestHeaders) requestLines.AppendLine(header.Name + ':' + header.Value);
{ }
var header = headerItem.Value; }
if (headerItem.Key != "Proxy-Authorization") }
{
requestLines.AppendLine(header.Name + ':' + header.Value); requestLines.AppendLine();
}
} string request = requestLines.ToString();
byte[] requestBytes = Encoding.ASCII.GetBytes(request);
//write non unique request headers
foreach (var headerItem in this.Request.NonUniqueRequestHeaders) await stream.WriteAsync(requestBytes, 0, requestBytes.Length);
{ await stream.FlushAsync();
var headers = headerItem.Value;
foreach (var header in headers) if (enable100ContinueBehaviour)
{ {
if (headerItem.Key != "Proxy-Authorization") if (this.Request.ExpectContinue)
{ {
requestLines.AppendLine(header.Name + ':' + header.Value); var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
} var responseStatusCode = httpResult[1].Trim();
} var responseStatusDescription = httpResult[2].Trim();
}
//find if server is willing for expect continue
requestLines.AppendLine(); if (responseStatusCode.Equals("100")
&& responseStatusDescription.ToLower().Equals("continue"))
string request = requestLines.ToString(); {
byte[] requestBytes = Encoding.ASCII.GetBytes(request); this.Request.Is100Continue = true;
await ServerConnection.StreamReader.ReadLineAsync();
await stream.WriteAsync(requestBytes, 0, requestBytes.Length); }
await stream.FlushAsync(); else if (responseStatusCode.Equals("417")
&& responseStatusDescription.ToLower().Equals("expectation failed"))
if (enable100ContinueBehaviour) {
{ this.Request.ExpectationFailed = true;
if (this.Request.ExpectContinue) await ServerConnection.StreamReader.ReadLineAsync();
{ }
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3); }
var responseStatusCode = httpResult[1].Trim(); }
var responseStatusDescription = httpResult[2].Trim(); }
//find if server is willing for expect continue /// <summary>
if (responseStatusCode.Equals("100") /// Receive & parse the http response from server
&& responseStatusDescription.ToLower().Equals("continue")) /// </summary>
{ /// <returns></returns>
this.Request.Is100Continue = true; internal async Task ReceiveResponse()
await ServerConnection.StreamReader.ReadLineAsync(); {
} //return if this is already read
else if (responseStatusCode.Equals("417") if (this.Response.ResponseStatusCode != null) return;
&& responseStatusDescription.ToLower().Equals("expectation failed"))
{ var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
this.Request.ExpectationFailed = true;
await ServerConnection.StreamReader.ReadLineAsync(); if (string.IsNullOrEmpty(httpResult[0]))
} {
} //Empty content in first-line, try again
} httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
} }
/// <summary> var httpVersion = httpResult[0].Trim().ToLower();
/// Receive & parse the http response from server
/// </summary> var version = new Version(1, 1);
/// <returns></returns> if (httpVersion == "http/1.0")
internal async Task ReceiveResponse() {
{ version = new Version(1, 0);
//return if this is already read }
if (this.Response.ResponseStatusCode != null) return;
this.Response.HttpVersion = version;
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3); this.Response.ResponseStatusCode = httpResult[1].Trim();
this.Response.ResponseStatusDescription = httpResult[2].Trim();
if (string.IsNullOrEmpty(httpResult[0]))
{ //For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request
//Empty content in first-line, try again if (this.Response.ResponseStatusCode.Equals("100")
httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3); && this.Response.ResponseStatusDescription.ToLower().Equals("continue"))
} {
//Read the next line after 100-continue
var httpVersion = httpResult[0].Trim().ToLower(); this.Response.Is100Continue = true;
this.Response.ResponseStatusCode = null;
var version = new Version(1, 1); await ServerConnection.StreamReader.ReadLineAsync();
if (httpVersion == "http/1.0") //now receive response
{ await ReceiveResponse();
version = new Version(1, 0); return;
} }
else if (this.Response.ResponseStatusCode.Equals("417")
this.Response.HttpVersion = version; && this.Response.ResponseStatusDescription.ToLower().Equals("expectation failed"))
this.Response.ResponseStatusCode = httpResult[1].Trim(); {
this.Response.ResponseStatusDescription = httpResult[2].Trim(); //read next line after expectation failed response
this.Response.ExpectationFailed = true;
//For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request this.Response.ResponseStatusCode = null;
if (this.Response.ResponseStatusCode.Equals("100") await ServerConnection.StreamReader.ReadLineAsync();
&& this.Response.ResponseStatusDescription.ToLower().Equals("continue")) //now receive response
{ await ReceiveResponse();
//Read the next line after 100-continue return;
this.Response.Is100Continue = true; }
this.Response.ResponseStatusCode = null;
await ServerConnection.StreamReader.ReadLineAsync(); //Read the Response headers
//now receive response //Read the response headers in to unique and non-unique header collections
await ReceiveResponse(); string tmpLine;
return; while (!string.IsNullOrEmpty(tmpLine = await ServerConnection.StreamReader.ReadLineAsync()))
} {
else if (this.Response.ResponseStatusCode.Equals("417") var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);
&& this.Response.ResponseStatusDescription.ToLower().Equals("expectation failed"))
{ var newHeader = new HttpHeader(header[0], header[1]);
//read next line after expectation failed response
this.Response.ExpectationFailed = true; //if header exist in non-unique header collection add it there
this.Response.ResponseStatusCode = null; if (Response.NonUniqueResponseHeaders.ContainsKey(newHeader.Name))
await ServerConnection.StreamReader.ReadLineAsync(); {
//now receive response Response.NonUniqueResponseHeaders[newHeader.Name].Add(newHeader);
await ReceiveResponse(); }
return; //if header is alread in unique header collection then move both to non-unique collection
} else if (Response.ResponseHeaders.ContainsKey(newHeader.Name))
{
//Read the Response headers var existing = Response.ResponseHeaders[newHeader.Name];
//Read the response headers in to unique and non-unique header collections
string tmpLine; var nonUniqueHeaders = new List<HttpHeader> {existing, newHeader};
while (!string.IsNullOrEmpty(tmpLine = await ServerConnection.StreamReader.ReadLineAsync()))
{ Response.NonUniqueResponseHeaders.Add(newHeader.Name, nonUniqueHeaders);
var header = tmpLine.Split(ProxyConstants.ColonSplit, 2); Response.ResponseHeaders.Remove(newHeader.Name);
}
var newHeader = new HttpHeader(header[0], header[1]); //add to unique header collection
else
//if header exist in non-unique header collection add it there {
if (Response.NonUniqueResponseHeaders.ContainsKey(newHeader.Name)) Response.ResponseHeaders.Add(newHeader.Name, newHeader);
{ }
Response.NonUniqueResponseHeaders[newHeader.Name].Add(newHeader); }
} }
//if header is alread in unique header collection then move both to non-unique collection }
else if (Response.ResponseHeaders.ContainsKey(newHeader.Name))
{
var existing = Response.ResponseHeaders[newHeader.Name];
var nonUniqueHeaders = new List<HttpHeader> {existing, newHeader};
Response.NonUniqueResponseHeaders.Add(newHeader.Name, nonUniqueHeaders);
Response.ResponseHeaders.Remove(newHeader.Name);
}
//add to unique header collection
else
{
Response.ResponseHeaders.Add(newHeader.Name, newHeader);
}
}
}
}
} }
\ No newline at end of file
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
namespace Titanium.Web.Proxy.Models namespace Titanium.Web.Proxy.Models
{ {
/// <summary> /// <summary>
/// An abstract endpoint where the proxy listens /// An abstract endpoint where the proxy listens
/// </summary> /// </summary>
public abstract class ProxyEndPoint public abstract class ProxyEndPoint
{ {
public ProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl) public ProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
{ {
this.IpAddress = IpAddress; this.IpAddress = IpAddress;
this.Port = Port; this.Port = Port;
this.EnableSsl = EnableSsl; this.EnableSsl = EnableSsl;
} }
public IPAddress IpAddress { get; internal set; } public IPAddress IpAddress { get; internal set; }
public int Port { get; internal set; } public int Port { get; internal set; }
public bool EnableSsl { get; internal set; } public bool EnableSsl { get; internal set; }
internal TcpListener listener { get; set; } public bool IpV6Enabled => IpAddress == IPAddress.IPv6Any
} || IpAddress == IPAddress.IPv6Loopback
|| IpAddress == IPAddress.IPv6None;
/// <summary>
/// A proxy endpoint that the client is aware of internal TcpListener listener { get; set; }
/// So client application know that it is communicating with a proxy server }
/// </summary>
public class ExplicitProxyEndPoint : ProxyEndPoint /// <summary>
{ /// A proxy endpoint that the client is aware of
internal bool IsSystemHttpProxy { get; set; } /// So client application know that it is communicating with a proxy server
internal bool IsSystemHttpsProxy { get; set; } /// </summary>
public class ExplicitProxyEndPoint : ProxyEndPoint
public List<string> ExcludedHttpsHostNameRegex { get; set; } {
internal bool IsSystemHttpProxy { get; set; }
public ExplicitProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl) internal bool IsSystemHttpsProxy { get; set; }
: base(IpAddress, Port, EnableSsl)
{ public List<string> ExcludedHttpsHostNameRegex { get; set; }
} public ExplicitProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
} : base(IpAddress, Port, EnableSsl)
{
/// <summary>
/// A proxy end point client is not aware of }
/// Usefull when requests are redirected to this proxy end point through port forwarding }
/// </summary>
public class TransparentProxyEndPoint : ProxyEndPoint /// <summary>
{ /// A proxy end point client is not aware of
//Name of the Certificate need to be sent (same as the hostname we want to proxy) /// Usefull when requests are redirected to this proxy end point through port forwarding
//This is valid only when UseServerNameIndication is set to false /// </summary>
public string GenericCertificateName { get; set; } public class TransparentProxyEndPoint : ProxyEndPoint
{
//Name of the Certificate need to be sent (same as the hostname we want to proxy)
// public bool UseServerNameIndication { get; set; } //This is valid only when UseServerNameIndication is set to false
public string GenericCertificateName { get; set; }
public TransparentProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
: base(IpAddress, Port, EnableSsl)
{ // public bool UseServerNameIndication { get; set; }
this.GenericCertificateName = "localhost";
} public TransparentProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
} : base(IpAddress, Port, EnableSsl)
{
} this.GenericCertificateName = "localhost";
}
}
}
namespace Titanium.Web.Proxy.Models using System;
{ using System.Net;
/// <summary>
/// An upstream proxy this proxy uses if any namespace Titanium.Web.Proxy.Models
/// </summary> {
public class ExternalProxy /// <summary>
{ /// An upstream proxy this proxy uses if any
public string UserName { get; set; } /// </summary>
public string Password { get; set; } public class ExternalProxy
public string HostName { get; set; } {
public int Port { get; set; } private static readonly Lazy<NetworkCredential> DefaultCredentials = new Lazy<NetworkCredential>(() => CredentialCache.DefaultNetworkCredentials);
}
} private string userName;
private string password;
public bool UseDefaultCredentials { get; set; }
public string UserName {
get { return UseDefaultCredentials ? DefaultCredentials.Value.UserName : userName; }
set
{
userName = value;
if (DefaultCredentials.Value.UserName != userName)
{
UseDefaultCredentials = false;
}
}
}
public string Password
{
get { return UseDefaultCredentials ? DefaultCredentials.Value.Password : password; }
set
{
password = value;
if (DefaultCredentials.Value.Password != password)
{
UseDefaultCredentials = false;
}
}
}
public string HostName { get; set; }
public int Port { get; set; }
}
}
...@@ -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),
......
...@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy ...@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public partial class ProxyServer : IDisposable public partial class ProxyServer : IDisposable
{ {
/// <summary> /// <summary>
/// Is the root certificate used by this proxy is valid? /// Is the root certificate used by this proxy is valid?
/// </summary> /// </summary>
...@@ -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,6 +369,28 @@ namespace Titanium.Web.Proxy ...@@ -358,6 +369,28 @@ namespace Titanium.Web.Proxy
proxyRunning = true; proxyRunning = true;
} }
/// <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> /// <summary>
/// Stop this proxy server /// Stop this proxy server
/// </summary> /// </summary>
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Security; using System.Net.Security;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Authentication; using System.Security.Authentication;
...@@ -16,7 +17,6 @@ using Titanium.Web.Proxy.Shared; ...@@ -16,7 +17,6 @@ using Titanium.Web.Proxy.Shared;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using System.Text;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
...@@ -25,12 +25,33 @@ namespace Titanium.Web.Proxy ...@@ -25,12 +25,33 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
partial class ProxyServer partial class ProxyServer
{ {
private int FindProcessIdFromLocalPort(int port, IpVersion ipVersion)
{
var tcpRow = TcpHelper.GetExtendedTcpTable(ipVersion).FirstOrDefault(
row => row.LocalEndPoint.Port == port);
return tcpRow?.ProcessId ?? 0;
}
private int GetProcessIdFromPort(int port, bool ipV6Enabled)
{
var processId = FindProcessIdFromLocalPort(port, IpVersion.Ipv4);
if (processId > 0 && !ipV6Enabled)
{
return processId;
}
return FindProcessIdFromLocalPort(port, IpVersion.Ipv6);
}
//This is called when client is aware of proxy //This is called when client is aware of proxy
//So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy //So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient client) private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient)
{ {
Stream clientStream = client.GetStream(); var processId = GetProcessIdFromPort(((IPEndPoint)tcpClient.Client.RemoteEndPoint).Port, endPoint.IpV6Enabled);
Stream clientStream = tcpClient.GetStream();
clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000; clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000;
clientStream.WriteTimeout = ConnectionTimeOutSeconds * 1000; clientStream.WriteTimeout = ConnectionTimeOutSeconds * 1000;
...@@ -145,7 +166,6 @@ namespace Titanium.Web.Proxy ...@@ -145,7 +166,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,
...@@ -157,7 +177,7 @@ namespace Titanium.Web.Proxy ...@@ -157,7 +177,7 @@ namespace Titanium.Web.Proxy
return; return;
} }
//Now create the request //Now create the request
await HandleHttpSessionRequest(client, httpCmd, clientStream, clientStreamReader, clientStreamWriter, await HandleHttpSessionRequest(tcpClient, processId, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
httpRemoteUri.Scheme == Uri.UriSchemeHttps ? httpRemoteUri.Host : null, connectRequestHeaders, null, null); httpRemoteUri.Scheme == Uri.UriSchemeHttps ? httpRemoteUri.Host : null, connectRequestHeaders, null, null);
} }
catch (Exception ex) catch (Exception ex)
...@@ -170,6 +190,8 @@ namespace Titanium.Web.Proxy ...@@ -170,6 +190,8 @@ namespace Titanium.Web.Proxy
//So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client //So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient) private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient)
{ {
var processId = GetProcessIdFromPort(((IPEndPoint)tcpClient.Client.RemoteEndPoint).Port, endPoint.IpV6Enabled);
Stream clientStream = tcpClient.GetStream(); Stream clientStream = tcpClient.GetStream();
clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000; clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000;
...@@ -199,10 +221,7 @@ namespace Titanium.Web.Proxy ...@@ -199,10 +221,7 @@ namespace Titanium.Web.Proxy
} }
catch (Exception) catch (Exception)
{ {
if (sslStream != null) sslStream.Dispose();
{
sslStream.Dispose();
}
Dispose(sslStream, clientStreamReader, clientStreamWriter, null); Dispose(sslStream, clientStreamReader, clientStreamWriter, null);
return; return;
...@@ -219,11 +238,10 @@ namespace Titanium.Web.Proxy ...@@ -219,11 +238,10 @@ namespace Titanium.Web.Proxy
var httpCmd = await clientStreamReader.ReadLineAsync(); var httpCmd = await clientStreamReader.ReadLineAsync();
//Now create the request //Now create the request
await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter, await HandleHttpSessionRequest(tcpClient, processId, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
endPoint.EnableSsl ? endPoint.GenericCertificateName : null, null); endPoint.EnableSsl ? endPoint.GenericCertificateName : null, null);
} }
private async Task HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args, ExternalProxy customUpStreamHttpProxy, ExternalProxy customUpStreamHttpsProxy, bool CloseConnection) private async Task HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args, ExternalProxy customUpStreamHttpProxy, ExternalProxy customUpStreamHttpsProxy, bool CloseConnection)
{ {
try try
...@@ -256,10 +274,8 @@ namespace Titanium.Web.Proxy ...@@ -256,10 +274,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)
{ {
...@@ -344,23 +360,25 @@ namespace Titanium.Web.Proxy ...@@ -344,23 +360,25 @@ namespace Titanium.Web.Proxy
return; return;
} }
if (CloseConnection && connection != null) if (CloseConnection)
{ {
//dispose //dispose
connection.Dispose(); connection?.Dispose();
} }
} }
/// <summary> /// <summary>
/// This is the core request handler method for a particular connection from client /// This is the core request handler method for a particular connection from client
/// </summary> /// </summary>
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="processId"></param>
/// <param name="httpCmd"></param> /// <param name="httpCmd"></param>
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param> /// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param> /// <param name="clientStreamWriter"></param>
/// <param name="httpsHostName"></param> /// <param name="httpsHostName"></param>
/// <returns></returns> /// <returns></returns>
private async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream, private async Task HandleHttpSessionRequest(TcpClient client, int processId, string httpCmd, Stream clientStream,
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName, List<HttpHeader> connectHeaders, ExternalProxy customUpStreamHttpProxy = null, ExternalProxy customUpStreamHttpsProxy = null) CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName, List<HttpHeader> connectHeaders, ExternalProxy customUpStreamHttpProxy = null, ExternalProxy customUpStreamHttpsProxy = null)
{ {
TcpConnection connection = null; TcpConnection connection = null;
...@@ -378,6 +396,8 @@ namespace Titanium.Web.Proxy ...@@ -378,6 +396,8 @@ namespace Titanium.Web.Proxy
var args = new SessionEventArgs(BUFFER_SIZE, HandleHttpSessionResponse); var args = new SessionEventArgs(BUFFER_SIZE, HandleHttpSessionResponse);
args.ProxyClient.TcpClient = client; args.ProxyClient.TcpClient = client;
args.WebSession.ConnectHeaders = connectHeaders; args.WebSession.ConnectHeaders = connectHeaders;
args.WebSession.ProcessId = processId;
try try
{ {
//break up the line into three components (method, remote URL & Http Version) //break up the line into three components (method, remote URL & Http Version)
...@@ -450,7 +470,6 @@ namespace Titanium.Web.Proxy ...@@ -450,7 +470,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;
...@@ -558,7 +577,6 @@ namespace Titanium.Web.Proxy ...@@ -558,7 +577,6 @@ namespace Titanium.Web.Proxy
webRequest.Request.RequestHeaders = requestHeaders; webRequest.Request.RequestHeaders = requestHeaders;
} }
/// <summary> /// <summary>
/// This is called when the request is PUT/POST to read the body /// This is called when the request is PUT/POST to read the body
/// </summary> /// </summary>
...@@ -580,9 +598,7 @@ namespace Titanium.Web.Proxy ...@@ -580,9 +598,7 @@ namespace Titanium.Web.Proxy
else if (args.WebSession.Request.IsChunked) else if (args.WebSession.Request.IsChunked)
{ {
await args.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(BUFFER_SIZE, postStream); await args.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(BUFFER_SIZE, postStream);
} }
} }
} }
} }
\ No newline at end of file
using System.Net; using System.Net;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Tcp namespace Titanium.Web.Proxy.Tcp
{ {
/// <summary> /// <summary>
/// Represents a managed interface of IP Helper API TcpRow struct /// Represents a managed interface of IP Helper API TcpRow struct
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/> /// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary> /// </summary>
internal class TcpRow internal class TcpRow
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TcpRow"/> class. /// Initializes a new instance of the <see cref="TcpRow"/> class.
/// </summary> /// </summary>
/// <param name="tcpRow">TcpRow struct.</param> /// <param name="tcpRow">TcpRow struct.</param>
public TcpRow(NativeMethods.TcpRow tcpRow) public TcpRow(NativeMethods.TcpRow tcpRow)
{ {
ProcessId = tcpRow.owningPid; ProcessId = tcpRow.owningPid;
int localPort = (tcpRow.localPort1 << 8) + (tcpRow.localPort2) + (tcpRow.localPort3 << 24) + (tcpRow.localPort4 << 16); int localPort = (tcpRow.localPort1 << 8) + (tcpRow.localPort2) + (tcpRow.localPort3 << 24) + (tcpRow.localPort4 << 16);
long localAddress = tcpRow.localAddr; long localAddress = tcpRow.localAddr;
LocalEndPoint = new IPEndPoint(localAddress, localPort); LocalEndPoint = new IPEndPoint(localAddress, localPort);
}
int remotePort = (tcpRow.remotePort1 << 8) + (tcpRow.remotePort2) + (tcpRow.remotePort3 << 24) + (tcpRow.remotePort4 << 16);
/// <summary> long remoteAddress = tcpRow.remoteAddr;
/// Gets the local end point. RemoteEndPoint = new IPEndPoint(remoteAddress, remotePort);
/// </summary> }
public IPEndPoint LocalEndPoint { get; private set; }
/// <summary>
/// <summary> /// Gets the local end point.
/// Gets the process identifier. /// </summary>
/// </summary> public IPEndPoint LocalEndPoint { get; private set; }
public int ProcessId { get; private set; }
} /// <summary>
/// Gets the remote end point.
/// </summary>
public IPEndPoint RemoteEndPoint { get; private set; }
/// <summary>
/// Gets the process identifier.
/// </summary>
public int ProcessId { get; private set; }
}
} }
\ No newline at end of file
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