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.Collections.Generic;
using System.IO;
using System.Text;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Decompression;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Http.Responses;
using Titanium.Web.Proxy.Extensions;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Network;
using System.Net;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.EventArguments
{
/// <summary>
/// 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 ends when client terminates connection to proxy
/// or when server terminates connection from proxy
/// </summary>
public class SessionEventArgs : EventArgs, IDisposable
{
/// <summary>
/// Size of Buffers used by this object
/// </summary>
private readonly int bufferSize;
/// <summary>
/// Holds a reference to proxy response handler method
/// </summary>
private readonly Func<SessionEventArgs, Task> httpResponseHandler;
/// <summary>
/// Holds a reference to client
/// </summary>
internal ProxyClient ProxyClient { get; set; }
//Should we send a rerequest
public bool ReRequest
{
get;
set;
}
/// <summary>
/// Does this session uses SSL
/// </summary>
public bool IsHttps => WebSession.Request.RequestUri.Scheme == Uri.UriSchemeHttps;
public IPEndPoint ClientEndPoint => (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint;
/// <summary>
/// A web session corresponding to a single request/response sequence
/// within a proxy connection
/// </summary>
public HttpWebClient WebSession { get; set; }
public ExternalProxy CustomUpStreamHttpProxyUsed { get; set; }
public ExternalProxy CustomUpStreamHttpsProxyUsed { get; set; }
/// <summary>
/// Constructor to initialize the proxy
/// </summary>
internal SessionEventArgs(int bufferSize, Func<SessionEventArgs, Task> httpResponseHandler)
{
this.bufferSize = bufferSize;
this.httpResponseHandler = httpResponseHandler;
ProxyClient = new ProxyClient();
WebSession = new HttpWebClient();
}
/// <summary>
/// Read request body content as bytes[] for current session
/// </summary>
private async Task ReadRequestBody()
{
//GET request don't have a request body to read
if ((WebSession.Request.Method.ToUpper() != "POST" && WebSession.Request.Method.ToUpper() != "PUT"))
{
throw new BodyNotFoundException("Request don't have a body." +
"Please verify that this request is a Http POST/PUT and request " +
"content length is greater than zero before accessing the body.");
}
//Caching check
if (WebSession.Request.RequestBody == null)
{
//If chunked then its easy just read the whole body with the content length mentioned in the request header
using (var requestBodyStream = new MemoryStream())
{
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if (WebSession.Request.IsChunked)
{
await this.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(bufferSize, requestBodyStream);
}
else
{
//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 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,
WebSession.Request.ContentLength);
}
else if (WebSession.Request.HttpVersion.Major == 1 && WebSession.Request.HttpVersion.Minor == 0)
{
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, requestBodyStream, long.MaxValue);
}
}
WebSession.Request.RequestBody = await GetDecompressedResponseBody(WebSession.Request.ContentEncoding,
requestBodyStream.ToArray());
}
//Now set the flag to true
//So that next time we can deliver body from cache
WebSession.Request.RequestBodyRead = true;
}
}
/// <summary>
/// Read response body as byte[] for current response
/// </summary>
private async Task ReadResponseBody()
{
//If not already read (not cached yet)
if (WebSession.Response.ResponseBody == null)
{
using (var responseBodyStream = new MemoryStream())
{
//If chuncked the read chunk by chunk until we hit chunk end symbol
if (WebSession.Response.IsChunked)
{
await WebSession.ServerConnection.StreamReader.CopyBytesToStreamChunked(bufferSize, responseBodyStream);
}
else
{
if (WebSession.Response.ContentLength > 0)
{
//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,
WebSession.Response.ContentLength);
}
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);
}
}
WebSession.Response.ResponseBody = await GetDecompressedResponseBody(WebSession.Response.ContentEncoding,
responseBodyStream.ToArray());
}
//set this to true for caching
WebSession.Response.ResponseBodyRead = true;
}
}
/// <summary>
/// Gets the request body as bytes
/// </summary>
/// <returns></returns>
public async Task<byte[]> GetRequestBody()
{
if (!WebSession.Request.RequestBodyRead)
{
if (WebSession.Request.RequestLocked)
{
throw new Exception("You cannot call this function after request is made to server.");
}
await ReadRequestBody();
}
return WebSession.Request.RequestBody;
}
/// <summary>
/// Gets the request body as string
/// </summary>
/// <returns></returns>
public async Task<string> GetRequestBodyAsString()
{
if (!WebSession.Request.RequestBodyRead)
{
if (WebSession.Request.RequestLocked)
{
throw new Exception("You cannot call this function after request is made to server.");
}
await ReadRequestBody();
}
//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));
}
/// <summary>
/// Sets the request body
/// </summary>
/// <param name="body"></param>
public async Task SetRequestBody(byte[] body)
{
if (WebSession.Request.RequestLocked)
{
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
if (!WebSession.Request.RequestBodyRead)
{
await ReadRequestBody();
}
WebSession.Request.RequestBody = body;
if (WebSession.Request.IsChunked == false)
{
WebSession.Request.ContentLength = body.Length;
}
else
{
WebSession.Request.ContentLength = -1;
}
}
/// <summary>
/// Sets the body with the specified string
/// </summary>
/// <param name="body"></param>
public async Task SetRequestBodyString(string body)
{
if (WebSession.Request.RequestLocked)
{
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
if (!WebSession.Request.RequestBodyRead)
{
await ReadRequestBody();
}
await SetRequestBody(WebSession.Request.Encoding.GetBytes(body));
}
/// <summary>
/// Gets the response body as byte array
/// </summary>
/// <returns></returns>
public async Task<byte[]> GetResponseBody()
{
if (!WebSession.Request.RequestLocked)
{
throw new Exception("You cannot call this function before request is made to server.");
}
await ReadResponseBody();
return WebSession.Response.ResponseBody;
}
/// <summary>
/// Gets the response body as string
/// </summary>
/// <returns></returns>
public async Task<string> GetResponseBodyAsString()
{
if (!WebSession.Request.RequestLocked)
{
throw new Exception("You cannot call this function before request is made to server.");
}
await GetResponseBody();
return WebSession.Response.ResponseBodyString ??
(WebSession.Response.ResponseBodyString = WebSession.Response.Encoding.GetString(WebSession.Response.ResponseBody));
}
/// <summary>
/// Set the response body bytes
/// </summary>
/// <param name="body"></param>
public async Task SetResponseBody(byte[] body)
{
if (!WebSession.Request.RequestLocked)
{
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
if (WebSession.Response.ResponseBody == null)
{
await GetResponseBody();
}
WebSession.Response.ResponseBody = body;
//If there is a content length header update it
if (WebSession.Response.IsChunked == false)
{
WebSession.Response.ContentLength = body.Length;
}
else
{
WebSession.Response.ContentLength = -1;
}
}
/// <summary>
/// Replace the response body with the specified string
/// </summary>
/// <param name="body"></param>
public async Task SetResponseBodyString(string body)
{
if (!WebSession.Request.RequestLocked)
{
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
if (WebSession.Response.ResponseBody == null)
{
await GetResponseBody();
}
var bodyBytes = WebSession.Response.Encoding.GetBytes(body);
await SetResponseBody(bodyBytes);
}
private async Task<byte[]> GetDecompressedResponseBody(string encodingType, byte[] responseBodyStream)
{
var decompressionFactory = new DecompressionFactory();
var decompressor = decompressionFactory.Create(encodingType);
return await decompressor.Decompress(responseBodyStream, bufferSize);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified HTML string to client
/// and ignore the request
/// </summary>
/// <param name="html"></param>
public async Task Ok(string html)
{
await Ok(html, null);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified HTML string to client
/// and ignore the request
/// </summary>
/// <param name="html"></param>
/// <param name="headers"></param>
public async Task Ok(string html, Dictionary<string, HttpHeader> headers)
{
if (WebSession.Request.RequestLocked)
{
throw new Exception("You cannot call this function after request is made to server.");
}
if (html == null)
{
html = string.Empty;
}
var result = Encoding.Default.GetBytes(html);
await Ok(result, headers);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified byte[] to client
/// and ignore the request
/// </summary>
/// <param name="result"></param>
public async Task Ok(byte[] result)
{
await Ok(result, null);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified byte[] to client
/// and ignore the request
/// </summary>
/// <param name="result"></param>
/// <param name="headers"></param>
public async Task Ok(byte[] result, Dictionary<string, HttpHeader> headers)
{
var response = new OkResponse();
if (headers != null && headers.Count > 0)
{
response.ResponseHeaders = headers;
}
response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseBody = result;
await Respond(response);
WebSession.Request.CancelRequest = true;
}
public async Task Redirect(string url)
{
var response = new RedirectResponse();
response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseHeaders.Add("Location", new Models.HttpHeader("Location", url));
response.ResponseBody = Encoding.ASCII.GetBytes(string.Empty);
await Respond(response);
WebSession.Request.CancelRequest = true;
}
/// a generic responder method
public async Task Respond(Response response)
{
WebSession.Request.RequestLocked = true;
response.ResponseLocked = true;
response.ResponseBodyRead = true;
WebSession.Response = response;
await httpResponseHandler(this);
}
/// <summary>
/// implement any cleanup here
/// </summary>
public void Dispose()
{
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Decompression;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Http.Responses;
using Titanium.Web.Proxy.Extensions;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Network;
using System.Net;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.EventArguments
{
/// <summary>
/// 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 ends when client terminates connection to proxy
/// or when server terminates connection from proxy
/// </summary>
public class SessionEventArgs : EventArgs, IDisposable
{
/// <summary>
/// Size of Buffers used by this object
/// </summary>
private readonly int bufferSize;
/// <summary>
/// Holds a reference to proxy response handler method
/// </summary>
private readonly Func<SessionEventArgs, Task> httpResponseHandler;
/// <summary>
/// Holds a reference to client
/// </summary>
internal ProxyClient ProxyClient { get; set; }
//Should we send a rerequest
public bool ReRequest
{
get;
set;
}
/// <summary>
/// Does this session uses SSL
/// </summary>
public bool IsHttps => WebSession.Request.RequestUri.Scheme == Uri.UriSchemeHttps;
public IPEndPoint ClientEndPoint => (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint;
/// <summary>
/// A web session corresponding to a single request/response sequence
/// within a proxy connection
/// </summary>
public HttpWebClient WebSession { get; set; }
public ExternalProxy CustomUpStreamHttpProxyUsed { get; set; }
public ExternalProxy CustomUpStreamHttpsProxyUsed { get; set; }
/// <summary>
/// Constructor to initialize the proxy
/// </summary>
internal SessionEventArgs(int bufferSize, Func<SessionEventArgs, Task> httpResponseHandler)
{
this.bufferSize = bufferSize;
this.httpResponseHandler = httpResponseHandler;
ProxyClient = new ProxyClient();
WebSession = new HttpWebClient();
}
/// <summary>
/// Read request body content as bytes[] for current session
/// </summary>
private async Task ReadRequestBody()
{
//GET request don't have a request body to read
if ((WebSession.Request.Method.ToUpper() != "POST" && WebSession.Request.Method.ToUpper() != "PUT"))
{
throw new BodyNotFoundException("Request don't have a body." +
"Please verify that this request is a Http POST/PUT and request " +
"content length is greater than zero before accessing the body.");
}
//Caching check
if (WebSession.Request.RequestBody == null)
{
//If chunked then its easy just read the whole body with the content length mentioned in the request header
using (var requestBodyStream = new MemoryStream())
{
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if (WebSession.Request.IsChunked)
{
await this.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(bufferSize, requestBodyStream);
}
else
{
//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 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,
WebSession.Request.ContentLength);
}
else if (WebSession.Request.HttpVersion.Major == 1 && WebSession.Request.HttpVersion.Minor == 0)
{
await WebSession.ServerConnection.StreamReader.CopyBytesToStream(bufferSize, requestBodyStream, long.MaxValue);
}
}
WebSession.Request.RequestBody = await GetDecompressedResponseBody(WebSession.Request.ContentEncoding,
requestBodyStream.ToArray());
}
//Now set the flag to true
//So that next time we can deliver body from cache
WebSession.Request.RequestBodyRead = true;
}
}
/// <summary>
/// Read response body as byte[] for current response
/// </summary>
private async Task ReadResponseBody()
{
//If not already read (not cached yet)
if (WebSession.Response.ResponseBody == null)
{
using (var responseBodyStream = new MemoryStream())
{
//If chuncked the read chunk by chunk until we hit chunk end symbol
if (WebSession.Response.IsChunked)
{
await WebSession.ServerConnection.StreamReader.CopyBytesToStreamChunked(bufferSize, responseBodyStream);
}
else
{
if (WebSession.Response.ContentLength > 0)
{
//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,
WebSession.Response.ContentLength);
}
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);
}
}
WebSession.Response.ResponseBody = await GetDecompressedResponseBody(WebSession.Response.ContentEncoding,
responseBodyStream.ToArray());
}
//set this to true for caching
WebSession.Response.ResponseBodyRead = true;
}
}
/// <summary>
/// Gets the request body as bytes
/// </summary>
/// <returns></returns>
public async Task<byte[]> GetRequestBody()
{
if (!WebSession.Request.RequestBodyRead)
{
if (WebSession.Request.RequestLocked)
{
throw new Exception("You cannot call this function after request is made to server.");
}
await ReadRequestBody();
}
return WebSession.Request.RequestBody;
}
/// <summary>
/// Gets the request body as string
/// </summary>
/// <returns></returns>
public async Task<string> GetRequestBodyAsString()
{
if (!WebSession.Request.RequestBodyRead)
{
if (WebSession.Request.RequestLocked)
{
throw new Exception("You cannot call this function after request is made to server.");
}
await ReadRequestBody();
}
//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));
}
/// <summary>
/// Sets the request body
/// </summary>
/// <param name="body"></param>
public async Task SetRequestBody(byte[] body)
{
if (WebSession.Request.RequestLocked)
{
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
if (!WebSession.Request.RequestBodyRead)
{
await ReadRequestBody();
}
WebSession.Request.RequestBody = body;
if (WebSession.Request.IsChunked == false)
{
WebSession.Request.ContentLength = body.Length;
}
else
{
WebSession.Request.ContentLength = -1;
}
}
/// <summary>
/// Sets the body with the specified string
/// </summary>
/// <param name="body"></param>
public async Task SetRequestBodyString(string body)
{
if (WebSession.Request.RequestLocked)
{
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
if (!WebSession.Request.RequestBodyRead)
{
await ReadRequestBody();
}
await SetRequestBody(WebSession.Request.Encoding.GetBytes(body));
}
/// <summary>
/// Gets the response body as byte array
/// </summary>
/// <returns></returns>
public async Task<byte[]> GetResponseBody()
{
if (!WebSession.Request.RequestLocked)
{
throw new Exception("You cannot call this function before request is made to server.");
}
await ReadResponseBody();
return WebSession.Response.ResponseBody;
}
/// <summary>
/// Gets the response body as string
/// </summary>
/// <returns></returns>
public async Task<string> GetResponseBodyAsString()
{
if (!WebSession.Request.RequestLocked)
{
throw new Exception("You cannot call this function before request is made to server.");
}
await GetResponseBody();
return WebSession.Response.ResponseBodyString ??
(WebSession.Response.ResponseBodyString = WebSession.Response.Encoding.GetString(WebSession.Response.ResponseBody));
}
/// <summary>
/// Set the response body bytes
/// </summary>
/// <param name="body"></param>
public async Task SetResponseBody(byte[] body)
{
if (!WebSession.Request.RequestLocked)
{
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
if (WebSession.Response.ResponseBody == null)
{
await GetResponseBody();
}
WebSession.Response.ResponseBody = body;
//If there is a content length header update it
if (WebSession.Response.IsChunked == false)
{
WebSession.Response.ContentLength = body.Length;
}
else
{
WebSession.Response.ContentLength = -1;
}
}
/// <summary>
/// Replace the response body with the specified string
/// </summary>
/// <param name="body"></param>
public async Task SetResponseBodyString(string body)
{
if (!WebSession.Request.RequestLocked)
{
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
if (WebSession.Response.ResponseBody == null)
{
await GetResponseBody();
}
var bodyBytes = WebSession.Response.Encoding.GetBytes(body);
await SetResponseBody(bodyBytes);
}
private async Task<byte[]> GetDecompressedResponseBody(string encodingType, byte[] responseBodyStream)
{
var decompressionFactory = new DecompressionFactory();
var decompressor = decompressionFactory.Create(encodingType);
return await decompressor.Decompress(responseBodyStream, bufferSize);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified HTML string to client
/// and ignore the request
/// </summary>
/// <param name="html"></param>
public async Task Ok(string html)
{
await Ok(html, null);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified HTML string to client
/// and ignore the request
/// </summary>
/// <param name="html"></param>
/// <param name="headers"></param>
public async Task Ok(string html, Dictionary<string, HttpHeader> headers)
{
if (WebSession.Request.RequestLocked)
{
throw new Exception("You cannot call this function after request is made to server.");
}
if (html == null)
{
html = string.Empty;
}
var result = Encoding.Default.GetBytes(html);
await Ok(result, headers);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified byte[] to client
/// and ignore the request
/// </summary>
/// <param name="result"></param>
public async Task Ok(byte[] result)
{
await Ok(result, null);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified byte[] to client
/// and ignore the request
/// </summary>
/// <param name="result"></param>
/// <param name="headers"></param>
public async Task Ok(byte[] result, Dictionary<string, HttpHeader> headers)
{
var response = new OkResponse();
if (headers != null && headers.Count > 0)
{
response.ResponseHeaders = headers;
}
response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseBody = result;
await Respond(response);
WebSession.Request.CancelRequest = true;
}
public async Task Redirect(string url)
{
var response = new RedirectResponse();
response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseHeaders.Add("Location", new Models.HttpHeader("Location", url));
response.ResponseBody = Encoding.ASCII.GetBytes(string.Empty);
await Respond(response);
WebSession.Request.CancelRequest = true;
}
/// a generic responder method
public async Task Respond(Response response)
{
WebSession.Request.RequestLocked = true;
response.ResponseLocked = true;
response.ResponseBodyRead = true;
WebSession.Response = response;
await httpResponseHandler(this);
}
/// <summary>
/// implement any cleanup here
/// </summary>
public void Dispose()
{
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Net.Security;
using System.Runtime.InteropServices;
using System.Security.Authentication;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Tcp;
namespace Titanium.Web.Proxy.Helpers
{
internal partial class NativeMethods
{
internal const int AfInet = 2;
internal enum TcpTableType
{
BasicListener,
BasicConnections,
BasicAll,
OwnerPidListener,
OwnerPidConnections,
OwnerPidAll,
OwnerModuleListener,
OwnerModuleConnections,
OwnerModuleAll,
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366921.aspx"/>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct TcpTable
{
public uint length;
public TcpRow row;
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct TcpRow
{
public TcpState state;
public uint localAddr;
public byte localPort1;
public byte localPort2;
public byte localPort3;
public byte localPort4;
public uint remoteAddr;
public byte remotePort1;
public byte remotePort2;
public byte remotePort3;
public byte remotePort4;
public int owningPid;
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/>
/// </summary>
[DllImport("iphlpapi.dll", SetLastError = true)]
internal static extern uint GetExtendedTcpTable(IntPtr tcpTable, ref int size, bool sort, int ipVersion, int tableClass, int reserved);
}
internal class TcpHelper
{
/// <summary>
/// Gets the extended TCP table.
/// </summary>
/// <returns>Collection of <see cref="TcpRow"/>.</returns>
internal static TcpTable GetExtendedTcpTable()
{
List<TcpRow> tcpRows = new List<TcpRow>();
IntPtr tcpTable = IntPtr.Zero;
int tcpTableLength = 0;
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, NativeMethods.AfInet, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) != 0)
{
try
{
tcpTable = Marshal.AllocHGlobal(tcpTableLength);
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, NativeMethods.AfInet, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) == 0)
{
NativeMethods.TcpTable table = (NativeMethods.TcpTable)Marshal.PtrToStructure(tcpTable, typeof(NativeMethods.TcpTable));
IntPtr rowPtr = (IntPtr)((long)tcpTable + Marshal.SizeOf(table.length));
for (int i = 0; i < table.length; ++i)
{
tcpRows.Add(new TcpRow((NativeMethods.TcpRow)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow))));
rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow)));
}
}
}
finally
{
if (tcpTable != IntPtr.Zero)
{
Marshal.FreeHGlobal(tcpTable);
}
}
}
return new TcpTable(tcpRows);
}
/// <summary>
/// relays the input clientStream to the server at the specified host name & port with the given httpCmd & headers as prefix
/// Usefull for websocket requests
/// </summary>
/// <param name="bufferSize"></param>
/// <param name="connectionTimeOutSeconds"></param>
/// <param name="remoteHostName"></param>
/// <param name="httpCmd"></param>
/// <param name="httpVersion"></param>
/// <param name="requestHeaders"></param>
/// <param name="isHttps"></param>
/// <param name="remotePort"></param>
/// <param name="supportedProtocols"></param>
/// <param name="remoteCertificateValidationCallback"></param>
/// <param name="localCertificateSelectionCallback"></param>
/// <param name="clientStream"></param>
/// <param name="tcpConnectionFactory"></param>
/// <returns></returns>
internal static async Task SendRaw(int bufferSize, int connectionTimeOutSeconds,
string remoteHostName, int remotePort, string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders,
bool isHttps, SslProtocols supportedProtocols,
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
Stream clientStream, TcpConnectionFactory tcpConnectionFactory)
{
//prepare the prefix content
StringBuilder sb = null;
if (httpCmd != null || requestHeaders != null)
{
sb = new StringBuilder();
if (httpCmd != null)
{
sb.Append(httpCmd);
sb.Append(Environment.NewLine);
}
if (requestHeaders != null)
{
foreach (var header in requestHeaders.Select(t => t.Value.ToString()))
{
sb.Append(header);
sb.Append(Environment.NewLine);
}
}
sb.Append(Environment.NewLine);
}
var tcpConnection = await tcpConnectionFactory.CreateClient(bufferSize, connectionTimeOutSeconds,
remoteHostName, remotePort,
httpVersion, isHttps,
supportedProtocols, remoteCertificateValidationCallback, localCertificateSelectionCallback,
null, null, clientStream);
try
{
Stream tunnelStream = tcpConnection.Stream;
Task sendRelay;
//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);
}
finally
{
tcpConnection.Dispose();
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Net.Security;
using System.Runtime.InteropServices;
using System.Security.Authentication;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Tcp;
namespace Titanium.Web.Proxy.Helpers
{
internal enum IpVersion
{
Ipv4 = 1,
Ipv6 = 2,
}
internal partial class NativeMethods
{
internal const int AfInet = 2;
internal const int AfInet6 = 23;
internal enum TcpTableType
{
BasicListener,
BasicConnections,
BasicAll,
OwnerPidListener,
OwnerPidConnections,
OwnerPidAll,
OwnerModuleListener,
OwnerModuleConnections,
OwnerModuleAll,
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366921.aspx"/>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct TcpTable
{
public uint length;
public TcpRow row;
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct TcpRow
{
public TcpState state;
public uint localAddr;
public byte localPort1;
public byte localPort2;
public byte localPort3;
public byte localPort4;
public uint remoteAddr;
public byte remotePort1;
public byte remotePort2;
public byte remotePort3;
public byte remotePort4;
public int owningPid;
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/>
/// </summary>
[DllImport("iphlpapi.dll", SetLastError = true)]
internal static extern uint GetExtendedTcpTable(IntPtr tcpTable, ref int size, bool sort, int ipVersion, int tableClass, int reserved);
}
internal class TcpHelper
{
/// <summary>
/// Gets the extended TCP table.
/// </summary>
/// <returns>Collection of <see cref="TcpRow"/>.</returns>
internal static TcpTable GetExtendedTcpTable(IpVersion ipVersion)
{
List<TcpRow> tcpRows = new List<TcpRow>();
IntPtr tcpTable = IntPtr.Zero;
int tcpTableLength = 0;
var ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6;
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) != 0)
{
try
{
tcpTable = Marshal.AllocHGlobal(tcpTableLength);
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) == 0)
{
NativeMethods.TcpTable table = (NativeMethods.TcpTable)Marshal.PtrToStructure(tcpTable, typeof(NativeMethods.TcpTable));
IntPtr rowPtr = (IntPtr)((long)tcpTable + Marshal.SizeOf(table.length));
for (int i = 0; i < table.length; ++i)
{
tcpRows.Add(new TcpRow((NativeMethods.TcpRow)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow))));
rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow)));
}
}
}
finally
{
if (tcpTable != IntPtr.Zero)
{
Marshal.FreeHGlobal(tcpTable);
}
}
}
return new TcpTable(tcpRows);
}
/// <summary>
/// relays the input clientStream to the server at the specified host name & port with the given httpCmd & headers as prefix
/// Usefull for websocket requests
/// </summary>
/// <param name="bufferSize"></param>
/// <param name="connectionTimeOutSeconds"></param>
/// <param name="remoteHostName"></param>
/// <param name="httpCmd"></param>
/// <param name="httpVersion"></param>
/// <param name="requestHeaders"></param>
/// <param name="isHttps"></param>
/// <param name="remotePort"></param>
/// <param name="supportedProtocols"></param>
/// <param name="remoteCertificateValidationCallback"></param>
/// <param name="localCertificateSelectionCallback"></param>
/// <param name="clientStream"></param>
/// <param name="tcpConnectionFactory"></param>
/// <returns></returns>
internal static async Task SendRaw(int bufferSize, int connectionTimeOutSeconds,
string remoteHostName, int remotePort, string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders,
bool isHttps, SslProtocols supportedProtocols,
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
Stream clientStream, TcpConnectionFactory tcpConnectionFactory)
{
//prepare the prefix content
StringBuilder sb = null;
if (httpCmd != null || requestHeaders != null)
{
sb = new StringBuilder();
if (httpCmd != null)
{
sb.Append(httpCmd);
sb.Append(Environment.NewLine);
}
if (requestHeaders != null)
{
foreach (var header in requestHeaders.Select(t => t.Value.ToString()))
{
sb.Append(header);
sb.Append(Environment.NewLine);
}
}
sb.Append(Environment.NewLine);
}
var tcpConnection = await tcpConnectionFactory.CreateClient(bufferSize, connectionTimeOutSeconds,
remoteHostName, remotePort,
httpVersion, isHttps,
supportedProtocols, remoteCertificateValidationCallback, localCertificateSelectionCallback,
null, null, clientStream);
try
{
Stream tunnelStream = tcpConnection.Stream;
//Now async relay all server=>client & client=>server data
var sendRelay = clientStream.CopyToAsync(sb?.ToString() ?? string.Empty, tunnelStream);
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.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Shared;
using Titanium.Web.Proxy.Tcp;
namespace Titanium.Web.Proxy.Http
{
/// <summary>
/// Used to communicate with the server over HTTP(S)
/// </summary>
public class HttpWebClient
{
private int processId;
/// <summary>
/// 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; }
/// <summary>
/// PID of the process that is created the current session
/// </summary>
public int ProcessId
{
get
{
if (processId == 0)
{
TcpRow tcpRow = TcpHelper.GetExtendedTcpTable().TcpRows
.FirstOrDefault(row => row.LocalEndPoint.Port == ServerConnection.port);
processId = tcpRow?.ProcessId ?? -1;
}
return processId;
}
}
/// <summary>
/// Is Https?
/// </summary>
public bool IsHttps => this.Request.RequestUri.Scheme == Uri.UriSchemeHttps;
/// <summary>
/// Set the tcp connection to server used by this webclient
/// </summary>
/// <param name="connection">Instance of <see cref="TcpConnection"/></param>
internal void SetConnection(TcpConnection connection)
{
connection.LastAccess = DateTime.Now;
ServerConnection = connection;
}
internal HttpWebClient()
{
this.Request = new Request();
this.Response = new Response();
}
/// <summary>
/// Prepare & send the http(s) request
/// </summary>
/// <returns></returns>
internal async Task SendRequest(bool enable100ContinueBehaviour)
{
Stream stream = ServerConnection.Stream;
StringBuilder requestLines = new StringBuilder();
//prepare the request & headers
if ((ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false) || (ServerConnection.UpStreamHttpsProxy != null && ServerConnection.IsHttps == true))
{
requestLines.AppendLine(string.Join(" ", this.Request.Method, this.Request.RequestUri.AbsoluteUri, $"HTTP/{this.Request.HttpVersion.Major}.{this.Request.HttpVersion.Minor}"));
}
else
{
requestLines.AppendLine(string.Join(" ", this.Request.Method, this.Request.RequestUri.PathAndQuery, $"HTTP/{this.Request.HttpVersion.Major}.{this.Request.HttpVersion.Minor}"));
}
//Send Authentication to Upstream proxy if needed
if (ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false && !string.IsNullOrEmpty(ServerConnection.UpStreamHttpProxy.UserName) && ServerConnection.UpStreamHttpProxy.Password != null)
{
requestLines.AppendLine("Proxy-Connection: keep-alive");
requestLines.AppendLine("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(ServerConnection.UpStreamHttpProxy.UserName + ":" + ServerConnection.UpStreamHttpProxy.Password)));
}
//write request headers
foreach (var headerItem in this.Request.RequestHeaders)
{
var header = headerItem.Value;
if (headerItem.Key != "Proxy-Authorization")
{
requestLines.AppendLine(header.Name + ':' + header.Value);
}
}
//write non unique request headers
foreach (var headerItem in this.Request.NonUniqueRequestHeaders)
{
var headers = headerItem.Value;
foreach (var header in headers)
{
if (headerItem.Key != "Proxy-Authorization")
{
requestLines.AppendLine(header.Name + ':' + header.Value);
}
}
}
requestLines.AppendLine();
string request = requestLines.ToString();
byte[] requestBytes = Encoding.ASCII.GetBytes(request);
await stream.WriteAsync(requestBytes, 0, requestBytes.Length);
await stream.FlushAsync();
if (enable100ContinueBehaviour)
{
if (this.Request.ExpectContinue)
{
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
if (responseStatusCode.Equals("100")
&& responseStatusDescription.ToLower().Equals("continue"))
{
this.Request.Is100Continue = true;
await ServerConnection.StreamReader.ReadLineAsync();
}
else if (responseStatusCode.Equals("417")
&& responseStatusDescription.ToLower().Equals("expectation failed"))
{
this.Request.ExpectationFailed = true;
await ServerConnection.StreamReader.ReadLineAsync();
}
}
}
}
/// <summary>
/// Receive & parse the http response from server
/// </summary>
/// <returns></returns>
internal async Task ReceiveResponse()
{
//return if this is already read
if (this.Response.ResponseStatusCode != null) return;
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
if (string.IsNullOrEmpty(httpResult[0]))
{
//Empty content in first-line, try again
httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
}
var httpVersion = httpResult[0].Trim().ToLower();
var version = new Version(1, 1);
if (httpVersion == "http/1.0")
{
version = new Version(1, 0);
}
this.Response.HttpVersion = version;
this.Response.ResponseStatusCode = httpResult[1].Trim();
this.Response.ResponseStatusDescription = httpResult[2].Trim();
//For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request
if (this.Response.ResponseStatusCode.Equals("100")
&& this.Response.ResponseStatusDescription.ToLower().Equals("continue"))
{
//Read the next line after 100-continue
this.Response.Is100Continue = true;
this.Response.ResponseStatusCode = null;
await ServerConnection.StreamReader.ReadLineAsync();
//now receive response
await ReceiveResponse();
return;
}
else if (this.Response.ResponseStatusCode.Equals("417")
&& this.Response.ResponseStatusDescription.ToLower().Equals("expectation failed"))
{
//read next line after expectation failed response
this.Response.ExpectationFailed = true;
this.Response.ResponseStatusCode = null;
await ServerConnection.StreamReader.ReadLineAsync();
//now receive response
await ReceiveResponse();
return;
}
//Read the Response headers
//Read the response headers in to unique and non-unique header collections
string tmpLine;
while (!string.IsNullOrEmpty(tmpLine = await ServerConnection.StreamReader.ReadLineAsync()))
{
var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);
var newHeader = new HttpHeader(header[0], header[1]);
//if header exist in non-unique header collection add it there
if (Response.NonUniqueResponseHeaders.ContainsKey(newHeader.Name))
{
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);
}
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Http
{
/// <summary>
/// Used to communicate with the server over HTTP(S)
/// </summary>
public class HttpWebClient
{
private int processId;
/// <summary>
/// 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; }
/// <summary>
/// PID of the process that is created the current session
/// </summary>
public int ProcessId { get; internal set; }
/// <summary>
/// Is Https?
/// </summary>
public bool IsHttps => this.Request.RequestUri.Scheme == Uri.UriSchemeHttps;
/// <summary>
/// Set the tcp connection to server used by this webclient
/// </summary>
/// <param name="connection">Instance of <see cref="TcpConnection"/></param>
internal void SetConnection(TcpConnection connection)
{
connection.LastAccess = DateTime.Now;
ServerConnection = connection;
}
internal HttpWebClient()
{
this.Request = new Request();
this.Response = new Response();
}
/// <summary>
/// Prepare & send the http(s) request
/// </summary>
/// <returns></returns>
internal async Task SendRequest(bool enable100ContinueBehaviour)
{
Stream stream = ServerConnection.Stream;
StringBuilder requestLines = new StringBuilder();
//prepare the request & headers
if ((ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false) || (ServerConnection.UpStreamHttpsProxy != null && ServerConnection.IsHttps == true))
{
requestLines.AppendLine(string.Join(" ", this.Request.Method, this.Request.RequestUri.AbsoluteUri, $"HTTP/{this.Request.HttpVersion.Major}.{this.Request.HttpVersion.Minor}"));
}
else
{
requestLines.AppendLine(string.Join(" ", this.Request.Method, this.Request.RequestUri.PathAndQuery, $"HTTP/{this.Request.HttpVersion.Major}.{this.Request.HttpVersion.Minor}"));
}
//Send Authentication to Upstream proxy if needed
if (ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false && !string.IsNullOrEmpty(ServerConnection.UpStreamHttpProxy.UserName) && ServerConnection.UpStreamHttpProxy.Password != null)
{
requestLines.AppendLine("Proxy-Connection: keep-alive");
requestLines.AppendLine("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(ServerConnection.UpStreamHttpProxy.UserName + ":" + ServerConnection.UpStreamHttpProxy.Password)));
}
//write request headers
foreach (var headerItem in this.Request.RequestHeaders)
{
var header = headerItem.Value;
if (headerItem.Key != "Proxy-Authorization")
{
requestLines.AppendLine(header.Name + ':' + header.Value);
}
}
//write non unique request headers
foreach (var headerItem in this.Request.NonUniqueRequestHeaders)
{
var headers = headerItem.Value;
foreach (var header in headers)
{
if (headerItem.Key != "Proxy-Authorization")
{
requestLines.AppendLine(header.Name + ':' + header.Value);
}
}
}
requestLines.AppendLine();
string request = requestLines.ToString();
byte[] requestBytes = Encoding.ASCII.GetBytes(request);
await stream.WriteAsync(requestBytes, 0, requestBytes.Length);
await stream.FlushAsync();
if (enable100ContinueBehaviour)
{
if (this.Request.ExpectContinue)
{
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
if (responseStatusCode.Equals("100")
&& responseStatusDescription.ToLower().Equals("continue"))
{
this.Request.Is100Continue = true;
await ServerConnection.StreamReader.ReadLineAsync();
}
else if (responseStatusCode.Equals("417")
&& responseStatusDescription.ToLower().Equals("expectation failed"))
{
this.Request.ExpectationFailed = true;
await ServerConnection.StreamReader.ReadLineAsync();
}
}
}
}
/// <summary>
/// Receive & parse the http response from server
/// </summary>
/// <returns></returns>
internal async Task ReceiveResponse()
{
//return if this is already read
if (this.Response.ResponseStatusCode != null) return;
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
if (string.IsNullOrEmpty(httpResult[0]))
{
//Empty content in first-line, try again
httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
}
var httpVersion = httpResult[0].Trim().ToLower();
var version = new Version(1, 1);
if (httpVersion == "http/1.0")
{
version = new Version(1, 0);
}
this.Response.HttpVersion = version;
this.Response.ResponseStatusCode = httpResult[1].Trim();
this.Response.ResponseStatusDescription = httpResult[2].Trim();
//For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request
if (this.Response.ResponseStatusCode.Equals("100")
&& this.Response.ResponseStatusDescription.ToLower().Equals("continue"))
{
//Read the next line after 100-continue
this.Response.Is100Continue = true;
this.Response.ResponseStatusCode = null;
await ServerConnection.StreamReader.ReadLineAsync();
//now receive response
await ReceiveResponse();
return;
}
else if (this.Response.ResponseStatusCode.Equals("417")
&& this.Response.ResponseStatusDescription.ToLower().Equals("expectation failed"))
{
//read next line after expectation failed response
this.Response.ExpectationFailed = true;
this.Response.ResponseStatusCode = null;
await ServerConnection.StreamReader.ReadLineAsync();
//now receive response
await ReceiveResponse();
return;
}
//Read the Response headers
//Read the response headers in to unique and non-unique header collections
string tmpLine;
while (!string.IsNullOrEmpty(tmpLine = await ServerConnection.StreamReader.ReadLineAsync()))
{
var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);
var newHeader = new HttpHeader(header[0], header[1]);
//if header exist in non-unique header collection add it there
if (Response.NonUniqueResponseHeaders.ContainsKey(newHeader.Name))
{
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.Net;
using System.Net.Sockets;
namespace Titanium.Web.Proxy.Models
{
/// <summary>
/// An abstract endpoint where the proxy listens
/// </summary>
public abstract class ProxyEndPoint
{
public ProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
{
this.IpAddress = IpAddress;
this.Port = Port;
this.EnableSsl = EnableSsl;
}
public IPAddress IpAddress { get; internal set; }
public int Port { get; internal set; }
public bool EnableSsl { get; internal set; }
internal TcpListener listener { get; set; }
}
/// <summary>
/// A proxy endpoint that the client is aware of
/// So client application know that it is communicating with a proxy server
/// </summary>
public class ExplicitProxyEndPoint : ProxyEndPoint
{
internal bool IsSystemHttpProxy { get; set; }
internal bool IsSystemHttpsProxy { get; set; }
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
{
//Name of the Certificate need to be sent (same as the hostname we want to proxy)
//This is valid only when UseServerNameIndication is set to false
public string GenericCertificateName { get; set; }
// public bool UseServerNameIndication { get; set; }
public TransparentProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
: base(IpAddress, Port, EnableSsl)
{
this.GenericCertificateName = "localhost";
}
}
}
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
namespace Titanium.Web.Proxy.Models
{
/// <summary>
/// An abstract endpoint where the proxy listens
/// </summary>
public abstract class ProxyEndPoint
{
public ProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
{
this.IpAddress = IpAddress;
this.Port = Port;
this.EnableSsl = EnableSsl;
}
public IPAddress IpAddress { get; internal set; }
public int Port { get; internal set; }
public bool EnableSsl { get; internal set; }
public bool IpV6Enabled => IpAddress == IPAddress.IPv6Any
|| IpAddress == IPAddress.IPv6Loopback
|| IpAddress == IPAddress.IPv6None;
internal TcpListener listener { get; set; }
}
/// <summary>
/// A proxy endpoint that the client is aware of
/// So client application know that it is communicating with a proxy server
/// </summary>
public class ExplicitProxyEndPoint : ProxyEndPoint
{
internal bool IsSystemHttpProxy { get; set; }
internal bool IsSystemHttpsProxy { get; set; }
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
{
//Name of the Certificate need to be sent (same as the hostname we want to proxy)
//This is valid only when UseServerNameIndication is set to false
public string GenericCertificateName { get; set; }
// public bool UseServerNameIndication { get; set; }
public TransparentProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
: base(IpAddress, Port, EnableSsl)
{
this.GenericCertificateName = "localhost";
}
}
}
namespace Titanium.Web.Proxy.Models
{
/// <summary>
/// An upstream proxy this proxy uses if any
/// </summary>
public class ExternalProxy
{
public string UserName { get; set; }
public string Password { get; set; }
public string HostName { get; set; }
public int Port { get; set; }
}
}
using System;
using System.Net;
namespace Titanium.Web.Proxy.Models
{
/// <summary>
/// An upstream proxy this proxy uses if any
/// </summary>
public class ExternalProxy
{
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
internal ExternalProxy UpStreamHttpsProxy { get; set; }
internal string HostName { get; set; }
internal int port { get; set; }
internal int Port { get; set; }
internal bool IsHttps { get; set; }
......
......@@ -49,7 +49,7 @@ namespace Titanium.Web.Proxy.Network
SslStream sslStream = null;
//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);
stream = client.GetStream();
......@@ -100,17 +100,14 @@ namespace Titanium.Web.Proxy.Network
}
catch
{
if (sslStream != null)
{
sslStream.Dispose();
}
sslStream?.Dispose();
throw;
}
}
else
{
if (externalHttpProxy != null)
if (externalHttpProxy != null && externalHttpProxy.HostName != remoteHostName)
{
client = new TcpClient(externalHttpProxy.HostName, externalHttpProxy.Port);
stream = client.GetStream();
......@@ -133,7 +130,7 @@ namespace Titanium.Web.Proxy.Network
UpStreamHttpProxy = externalHttpProxy,
UpStreamHttpsProxy = externalHttpsProxy,
HostName = remoteHostName,
port = remotePort,
Port = remotePort,
IsHttps = isHttps,
TcpClient = client,
StreamReader = new CustomBinaryReader(stream),
......
......@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy
/// </summary>
public partial class ProxyServer : IDisposable
{
/// <summary>
/// Is the root certificate used by this proxy is valid?
/// </summary>
......@@ -186,6 +186,11 @@ namespace Titanium.Web.Proxy
/// </summary>
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>
/// Constructor
/// </summary>
......@@ -348,6 +353,12 @@ namespace Titanium.Web.Proxy
certificateCacheManager.TrustRootCertificate();
}
if (ForwardToUpstreamGateway && GetCustomUpStreamHttpProxyFunc == null && GetCustomUpStreamHttpsProxyFunc == null)
{
GetCustomUpStreamHttpProxyFunc = GetSystemUpStreamProxy;
GetCustomUpStreamHttpsProxyFunc = GetSystemUpStreamProxy;
}
foreach (var endPoint in ProxyEndPoints)
{
Listen(endPoint);
......@@ -358,6 +369,28 @@ namespace Titanium.Web.Proxy
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>
/// Stop this proxy server
/// </summary>
......
......@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
......@@ -16,7 +17,6 @@ using Titanium.Web.Proxy.Shared;
using Titanium.Web.Proxy.Http;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
using System.Text;
namespace Titanium.Web.Proxy
{
......@@ -25,12 +25,33 @@ namespace Titanium.Web.Proxy
/// </summary>
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
//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.WriteTimeout = ConnectionTimeOutSeconds * 1000;
......@@ -145,7 +166,6 @@ namespace Titanium.Web.Proxy
//write back successfull CONNECT response
await WriteConnectResponse(clientStreamWriter, version);
await TcpHelper.SendRaw(BUFFER_SIZE, ConnectionTimeOutSeconds, httpRemoteUri.Host, httpRemoteUri.Port,
httpCmd, version, null,
false, SupportedSslProtocols,
......@@ -157,7 +177,7 @@ namespace Titanium.Web.Proxy
return;
}
//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);
}
catch (Exception ex)
......@@ -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
private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient)
{
var processId = GetProcessIdFromPort(((IPEndPoint)tcpClient.Client.RemoteEndPoint).Port, endPoint.IpV6Enabled);
Stream clientStream = tcpClient.GetStream();
clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000;
......@@ -199,10 +221,7 @@ namespace Titanium.Web.Proxy
}
catch (Exception)
{
if (sslStream != null)
{
sslStream.Dispose();
}
sslStream.Dispose();
Dispose(sslStream, clientStreamReader, clientStreamWriter, null);
return;
......@@ -219,11 +238,10 @@ namespace Titanium.Web.Proxy
var httpCmd = await clientStreamReader.ReadLineAsync();
//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);
}
private async Task HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args, ExternalProxy customUpStreamHttpProxy, ExternalProxy customUpStreamHttpsProxy, bool CloseConnection)
{
try
......@@ -256,10 +274,8 @@ namespace Titanium.Web.Proxy
customUpStreamHttpProxy ?? UpStreamHttpProxy, customUpStreamHttpsProxy ?? UpStreamHttpsProxy, args.ProxyClient.ClientStream);
}
args.WebSession.Request.RequestLocked = true;
//If request was cancelled by user then dispose the client
if (args.WebSession.Request.CancelRequest)
{
......@@ -344,23 +360,25 @@ namespace Titanium.Web.Proxy
return;
}
if (CloseConnection && connection != null)
if (CloseConnection)
{
//dispose
connection.Dispose();
connection?.Dispose();
}
}
/// <summary>
/// This is the core request handler method for a particular connection from client
/// </summary>
/// <param name="client"></param>
/// <param name="processId"></param>
/// <param name="httpCmd"></param>
/// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param>
/// <param name="httpsHostName"></param>
/// <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)
{
TcpConnection connection = null;
......@@ -378,6 +396,8 @@ namespace Titanium.Web.Proxy
var args = new SessionEventArgs(BUFFER_SIZE, HandleHttpSessionResponse);
args.ProxyClient.TcpClient = client;
args.WebSession.ConnectHeaders = connectHeaders;
args.WebSession.ProcessId = processId;
try
{
//break up the line into three components (method, remote URL & Http Version)
......@@ -450,7 +470,6 @@ namespace Titanium.Web.Proxy
break;
}
PrepareRequestHeaders(args.WebSession.Request.RequestHeaders, args.WebSession);
args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority;
......@@ -558,7 +577,6 @@ namespace Titanium.Web.Proxy
webRequest.Request.RequestHeaders = requestHeaders;
}
/// <summary>
/// This is called when the request is PUT/POST to read the body
/// </summary>
......@@ -580,9 +598,7 @@ namespace Titanium.Web.Proxy
else if (args.WebSession.Request.IsChunked)
{
await args.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(BUFFER_SIZE, postStream);
}
}
}
}
\ No newline at end of file
using System.Net;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Tcp
{
/// <summary>
/// Represents a managed interface of IP Helper API TcpRow struct
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary>
internal class TcpRow
{
/// <summary>
/// Initializes a new instance of the <see cref="TcpRow"/> class.
/// </summary>
/// <param name="tcpRow">TcpRow struct.</param>
public TcpRow(NativeMethods.TcpRow tcpRow)
{
ProcessId = tcpRow.owningPid;
int localPort = (tcpRow.localPort1 << 8) + (tcpRow.localPort2) + (tcpRow.localPort3 << 24) + (tcpRow.localPort4 << 16);
long localAddress = tcpRow.localAddr;
LocalEndPoint = new IPEndPoint(localAddress, localPort);
}
/// <summary>
/// Gets the local end point.
/// </summary>
public IPEndPoint LocalEndPoint { get; private set; }
/// <summary>
/// Gets the process identifier.
/// </summary>
public int ProcessId { get; private set; }
}
using System.Net;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Tcp
{
/// <summary>
/// Represents a managed interface of IP Helper API TcpRow struct
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary>
internal class TcpRow
{
/// <summary>
/// Initializes a new instance of the <see cref="TcpRow"/> class.
/// </summary>
/// <param name="tcpRow">TcpRow struct.</param>
public TcpRow(NativeMethods.TcpRow tcpRow)
{
ProcessId = tcpRow.owningPid;
int localPort = (tcpRow.localPort1 << 8) + (tcpRow.localPort2) + (tcpRow.localPort3 << 24) + (tcpRow.localPort4 << 16);
long localAddress = tcpRow.localAddr;
LocalEndPoint = new IPEndPoint(localAddress, localPort);
int remotePort = (tcpRow.remotePort1 << 8) + (tcpRow.remotePort2) + (tcpRow.remotePort3 << 24) + (tcpRow.remotePort4 << 16);
long remoteAddress = tcpRow.remoteAddr;
RemoteEndPoint = new IPEndPoint(remoteAddress, remotePort);
}
/// <summary>
/// Gets the local end point.
/// </summary>
public IPEndPoint LocalEndPoint { 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