Unverified Commit a007c1c0 authored by honfika's avatar honfika Committed by GitHub

Merge pull request #395 from justcoding121/develop

merge to beta
parents 7eb8f297 ef38da77
......@@ -107,15 +107,18 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary>
/// Constructor to initialize the proxy
/// </summary>
internal SessionEventArgs(int bufferSize,
ProxyEndPoint endPoint,
ExceptionHandler exceptionFunc)
internal SessionEventArgs(int bufferSize, ProxyEndPoint endPoint, ExceptionHandler exceptionFunc)
: this(bufferSize, endPoint, exceptionFunc, null)
{
}
protected SessionEventArgs(int bufferSize, ProxyEndPoint endPoint, ExceptionHandler exceptionFunc, Request request)
{
this.bufferSize = bufferSize;
this.exceptionFunc = exceptionFunc;
ProxyClient = new ProxyClient();
WebSession = new HttpWebClient(bufferSize);
WebSession = new HttpWebClient(bufferSize, request);
LocalEndPoint = endPoint;
WebSession.ProcessId = new Lazy<int>(() =>
......@@ -138,6 +141,21 @@ namespace Titanium.Web.Proxy.EventArguments
});
}
private CustomBufferedStream GetStream(bool isRequest)
{
return isRequest ? ProxyClient.ClientStream : WebSession.ServerConnection.Stream;
}
private CustomBinaryReader GetStreamReader(bool isRequest)
{
return isRequest ? ProxyClient.ClientStreamReader : WebSession.ServerConnection.StreamReader;
}
private HttpWriter GetStreamWriter(bool isRequest)
{
return isRequest ? (HttpWriter)ProxyClient.ClientStreamWriter : WebSession.ServerConnection.StreamWriter;
}
/// <summary>
/// Read request body content as bytes[] for current session
/// </summary>
......@@ -150,7 +168,7 @@ namespace Titanium.Web.Proxy.EventArguments
//If not already read (not cached yet)
if (!request.IsBodyRead)
{
var body = await ReadBodyAsync(ProxyClient.ClientStreamReader, true);
var body = await ReadBodyAsync(true);
request.Body = body;
//Now set the flag to true
......@@ -165,8 +183,8 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
internal async Task ClearResponse()
{
//siphon out the body
await ReadResponseBodyAsync();
//syphon out the response body from server
await SyphonOutBodyAsync(false);
WebSession.Response = new Response();
}
......@@ -225,7 +243,7 @@ namespace Titanium.Web.Proxy.EventArguments
//If not already read (not cached yet)
if (!response.IsBodyRead)
{
var body = await ReadBodyAsync(WebSession.ServerConnection.StreamReader, false);
var body = await ReadBodyAsync(false);
response.Body = body;
//Now set the flag to true
......@@ -235,11 +253,12 @@ namespace Titanium.Web.Proxy.EventArguments
}
}
private async Task<byte[]> ReadBodyAsync(CustomBinaryReader reader, bool isRequest)
private async Task<byte[]> ReadBodyAsync(bool isRequest)
{
using (var bodyStream = new MemoryStream())
{
var writer = new HttpWriter(bodyStream, bufferSize);
if (isRequest)
{
await CopyRequestBodyAsync(writer, TransformationMode.Uncompress);
......@@ -253,21 +272,35 @@ namespace Titanium.Web.Proxy.EventArguments
}
}
internal async Task SyphonOutBodyAsync(bool isRequest)
{
var requestResponse = isRequest ? (RequestResponseBase)WebSession.Request : WebSession.Response;
if (requestResponse.IsBodyRead || !requestResponse.OriginalHasBody)
{
return;
}
using (var bodyStream = new MemoryStream())
{
var writer = new HttpWriter(bodyStream, bufferSize);
await CopyBodyAsync(isRequest, writer, TransformationMode.None, null);
}
}
/// <summary>
/// This is called when the request is PUT/POST/PATCH to read the body
/// </summary>
/// <returns></returns>
internal async Task CopyRequestBodyAsync(HttpWriter writer, TransformationMode transformation)
{
// End the operation
var request = WebSession.Request;
var reader = ProxyClient.ClientStreamReader;
long contentLength = request.ContentLength;
//send the request body bytes to server
if (contentLength > 0 && hasMulipartEventSubscribers && request.IsMultipartFormData)
{
var reader = GetStreamReader(true);
string boundary = HttpHelper.GetBoundaryFromContentType(request.ContentType);
using (var copyStream = new CopyStream(reader, writer, bufferSize))
......@@ -294,21 +327,22 @@ namespace Titanium.Web.Proxy.EventArguments
}
else
{
await CopyBodyAsync(ProxyClient.ClientStream, reader, writer, request, transformation, OnDataSent);
await CopyBodyAsync(true, writer, transformation, OnDataSent);
}
}
internal async Task CopyResponseBodyAsync(HttpWriter writer, TransformationMode transformation)
{
var response = WebSession.Response;
var reader = WebSession.ServerConnection.StreamReader;
await CopyBodyAsync(WebSession.ServerConnection.Stream, reader, writer, response, transformation, OnDataReceived);
await CopyBodyAsync(false, writer, transformation, OnDataReceived);
}
private async Task CopyBodyAsync(CustomBufferedStream stream, CustomBinaryReader reader, HttpWriter writer,
RequestResponseBase requestResponse, TransformationMode transformation, Action<byte[], int, int> onCopy)
private async Task CopyBodyAsync(bool isRequest, HttpWriter writer, TransformationMode transformation, Action<byte[], int, int> onCopy)
{
var stream = GetStream(isRequest);
var reader = GetStreamReader(isRequest);
var requestResponse = isRequest ? (RequestResponseBase)WebSession.Request : WebSession.Response;
bool isChunked = requestResponse.IsChunked;
long contentLength = requestResponse.ContentLength;
if (transformation == TransformationMode.None)
......@@ -440,7 +474,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// Sets the request body
/// </summary>
/// <param name="body"></param>
public async Task SetRequestBody(byte[] body)
public void SetRequestBody(byte[] body)
{
var request = WebSession.Request;
if (request.Locked)
......@@ -448,34 +482,21 @@ namespace Titanium.Web.Proxy.EventArguments
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 (!request.IsBodyRead)
{
await ReadRequestBodyAsync();
}
request.Body = body;
request.UpdateContentLength();
}
/// <summary>
/// Sets the body with the specified string
/// </summary>
/// <param name="body"></param>
public async Task SetRequestBodyString(string body)
public void SetRequestBodyString(string body)
{
if (WebSession.Request.Locked)
{
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.IsBodyRead)
{
await ReadRequestBodyAsync();
}
await SetRequestBody(WebSession.Request.Encoding.GetBytes(body));
SetRequestBody(WebSession.Request.Encoding.GetBytes(body));
}
/// <summary>
......@@ -510,7 +531,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// Set the response body bytes
/// </summary>
/// <param name="body"></param>
public async Task SetResponseBody(byte[] body)
public void SetResponseBody(byte[] body)
{
if (!WebSession.Request.Locked)
{
......@@ -518,39 +539,23 @@ namespace Titanium.Web.Proxy.EventArguments
}
var response = WebSession.Response;
//syphon out the response body from server before setting the new body
if (response.Body == null)
{
await GetResponseBody();
}
response.Body = body;
//If there is a content length header update it
response.UpdateContentLength();
}
/// <summary>
/// Replace the response body with the specified string
/// </summary>
/// <param name="body"></param>
public async Task SetResponseBodyString(string body)
public void SetResponseBodyString(string body)
{
if (!WebSession.Request.Locked)
{
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.IsBodyRead)
{
await GetResponseBody();
}
var bodyBytes = WebSession.Response.Encoding.GetBytes(body);
await SetResponseBody(bodyBytes);
SetResponseBody(bodyBytes);
}
/// <summary>
......@@ -651,17 +656,29 @@ namespace Titanium.Web.Proxy.EventArguments
{
if (WebSession.Request.Locked)
{
throw new Exception("You cannot call this function after request is made to server.");
}
if (WebSession.Response.Locked)
{
throw new Exception("You cannot call this function after response is sent to the client.");
}
WebSession.Request.Locked = true;
response.Locked = true;
response.TerminateResponse = WebSession.Response.TerminateResponse;
WebSession.Response = response;
}
else
{
WebSession.Request.Locked = true;
response.Locked = true;
response.IsBodyRead = true;
response.Locked = true;
WebSession.Response = response;
WebSession.Response = response;
WebSession.Request.CancelRequest = true;
}
}
WebSession.Request.CancelRequest = true;
public void TerminateServerConnection()
{
WebSession.Response.TerminateResponse = true;
}
/// <summary>
......
......@@ -11,9 +11,8 @@ namespace Titanium.Web.Proxy.EventArguments
public bool IsHttpsConnect { get; internal set; }
internal TunnelConnectSessionEventArgs(int bufferSize, ProxyEndPoint endPoint, ConnectRequest connectRequest, ExceptionHandler exceptionFunc)
: base(bufferSize, endPoint, exceptionFunc)
: base(bufferSize, endPoint, exceptionFunc, connectRequest)
{
WebSession.Request = connectRequest;
}
}
}
......@@ -20,7 +20,7 @@ namespace Titanium.Web.Proxy.Helpers
public async Task WriteResponseAsync(Response response, bool flush = true)
{
await WriteResponseStatusAsync(response.HttpVersion, response.StatusCode, response.StatusDescription);
await WriteHeadersAsync(response.Headers, flush);
await WriteAsync(response, flush);
}
/// <summary>
......
......@@ -5,7 +5,7 @@ using System.Text;
using System.Threading.Tasks;
using StreamExtended.Helpers;
using StreamExtended.Network;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Compression;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
......@@ -94,12 +94,9 @@ namespace Titanium.Web.Proxy.Helpers
/// <returns></returns>
public async Task WriteHeadersAsync(HeaderCollection headers, bool flush = true)
{
if (headers != null)
foreach (var header in headers)
{
foreach (var header in headers)
{
await header.WriteToStreamAsync(this);
}
await header.WriteToStreamAsync(this);
}
await WriteLineAsync();
......@@ -265,5 +262,22 @@ namespace Titanium.Web.Proxy.Helpers
onCopy?.Invoke(buffer, 0, bytesRead);
}
}
/// <summary>
/// Writes the request/response headers and body.
/// </summary>
/// <param name="requestResponse"></param>
/// <param name="flush"></param>
/// <returns></returns>
protected async Task WriteAsync(RequestResponseBase requestResponse, bool flush = true)
{
var body = requestResponse.CompressBodyAndUpdateContentLength();
await WriteHeadersAsync(requestResponse.Headers, flush);
if (body != null)
{
await WriteBodyAsync(body, requestResponse.IsChunked);
}
}
}
}
......@@ -38,7 +38,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Web Request.
/// </summary>
public Request Request { get; internal set; }
public Request Request { get; }
/// <summary>
/// Web Response.
......@@ -56,13 +56,13 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
public bool IsHttps => Request.IsHttps;
internal HttpWebClient(int bufferSize)
internal HttpWebClient(int bufferSize, Request request = null, Response response = null)
{
this.bufferSize = bufferSize;
RequestId = Guid.NewGuid();
Request = new Request();
Response = new Response();
Request = request ?? new Request();
Response = response ?? new Response();
}
/// <summary>
......
......@@ -37,7 +37,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Has request body?
/// </summary>
public bool HasBody
public override bool HasBody
{
get
{
......@@ -102,6 +102,11 @@ namespace Titanium.Web.Proxy.Http
internal override void EnsureBodyAvailable(bool throwWhenNotReadYet = true)
{
if (BodyInternal != null)
{
return;
}
//GET request don't have a request body to read
if (!HasBody)
{
......
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Compression;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
......@@ -15,7 +16,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Cached body content as byte array
/// </summary>
private byte[] body;
protected byte[] BodyInternal;
/// <summary>
/// Cached body as string
......@@ -126,16 +127,84 @@ namespace Titanium.Web.Proxy.Http
get
{
EnsureBodyAvailable();
return body;
return BodyInternal;
}
internal set
{
body = value;
BodyInternal = value;
bodyString = null;
//If there is a content length header update it
UpdateContentLength();
}
}
/// <summary>
/// Has the request/response body?
/// </summary>
public abstract bool HasBody { get; }
/// <summary>
/// Store weather the original request/response has body or not, since the user may change the parameters
/// </summary>
internal bool OriginalHasBody;
internal abstract void EnsureBodyAvailable(bool throwWhenNotReadYet = true);
/// <summary>
/// get the compressed body from given bytes
/// </summary>
/// <param name="encodingType"></param>
/// <param name="body"></param>
/// <returns></returns>
internal byte[] GetCompressedBody(string encodingType, byte[] body)
{
var compressor = CompressionFactory.GetCompression(encodingType);
using (var ms = new MemoryStream())
{
using (var zip = compressor.GetStream(ms))
{
zip.Write(body, 0, body.Length);
}
return ms.ToArray();
}
}
internal byte[] CompressBodyAndUpdateContentLength()
{
if (!IsBodyRead && BodyInternal == null)
{
return null;
}
bool isChunked = IsChunked;
string contentEncoding = ContentEncoding;
if (HasBody)
{
var body = Body;
if (contentEncoding != null && body != null)
{
body = GetCompressedBody(contentEncoding, body);
if (isChunked == false)
{
ContentLength = body.Length;
}
else
{
ContentLength = -1;
}
}
return body;
}
ContentLength = 0;
return null;
}
/// <summary>
/// Body as string
/// Use the encoding specified to decode the byte[] data to string
......@@ -155,7 +224,7 @@ namespace Titanium.Web.Proxy.Http
internal void UpdateContentLength()
{
ContentLength = IsChunked ? -1 : body.Length;
ContentLength = IsChunked ? -1 : BodyInternal?.Length ?? 0;
}
/// <summary>
......@@ -165,7 +234,7 @@ namespace Titanium.Web.Proxy.Http
{
if (!KeepBody)
{
body = null;
BodyInternal = null;
bodyString = null;
}
}
......
......@@ -26,7 +26,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Has response body?
/// </summary>
public bool HasBody
public override bool HasBody
{
get
{
......@@ -77,8 +77,15 @@ namespace Titanium.Web.Proxy.Http
}
}
internal bool TerminateResponse { get; set; }
internal override void EnsureBodyAvailable(bool throwWhenNotReadYet = true)
{
if (BodyInternal != null)
{
return;
}
if (!IsBodyRead && throwWhenNotReadYet)
{
throw new Exception("Response body is not read yet. " +
......@@ -121,6 +128,21 @@ namespace Titanium.Web.Proxy.Http
}
}
/// <summary>
/// Constructor.
/// </summary>
public Response()
{
}
/// <summary>
/// Constructor.
/// </summary>
public Response(byte[] body)
{
Body = body;
}
internal static string CreateResponseLine(Version version, int statusCode, string statusDescription)
{
return $"HTTP/{version.Major}.{version.Minor} {statusCode} {statusDescription}";
......
......@@ -15,5 +15,13 @@ namespace Titanium.Web.Proxy.Http.Responses
StatusCode = (int)HttpStatusCode.OK;
StatusDescription = "OK";
}
/// <summary>
/// Constructor.
/// </summary>
public OkResponse(byte[] body) : this()
{
Body = body;
}
}
}
......@@ -46,7 +46,6 @@ namespace Titanium.Web.Proxy
try
{
string connectHostname = null;
ConnectRequest connectRequest = null;
//Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request)
......@@ -297,7 +296,7 @@ namespace Titanium.Web.Proxy
string httpCmd = await clientStreamReader.ReadLineAsync();
if (string.IsNullOrEmpty(httpCmd))
{
break;
return;
}
var args = new SessionEventArgs(BufferSize, endPoint, ExceptionFunc)
......@@ -347,11 +346,12 @@ namespace Titanium.Web.Proxy
}
}
args.WebSession.Request.RequestUri = httpRemoteUri;
args.WebSession.Request.OriginalUrl = httpUrl;
var request = args.WebSession.Request;
request.RequestUri = httpRemoteUri;
request.OriginalUrl = httpUrl;
args.WebSession.Request.Method = httpMethod;
args.WebSession.Request.HttpVersion = version;
request.Method = httpMethod;
request.HttpVersion = version;
args.ProxyClient.ClientStream = clientStream;
args.ProxyClient.ClientStreamReader = clientStreamReader;
args.ProxyClient.ClientStreamWriter = clientStreamWriter;
......@@ -363,35 +363,40 @@ namespace Titanium.Web.Proxy
//send the response
await clientStreamWriter.WriteResponseAsync(args.WebSession.Response);
break;
return;
}
if (!isTransparentEndPoint)
{
PrepareRequestHeaders(args.WebSession.Request.Headers);
args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority;
PrepareRequestHeaders(request.Headers);
request.Host = request.RequestUri.Authority;
}
//if win auth is enabled
//we need a cache of request body
//so that we can send it after authentication in WinAuthHandler.cs
if (isWindowsAuthenticationEnabledAndSupported && args.WebSession.Request.HasBody)
if (isWindowsAuthenticationEnabledAndSupported && request.HasBody)
{
await args.GetRequestBody();
}
request.OriginalHasBody = request.HasBody;
//If user requested interception do it
await InvokeBeforeRequest(args);
var response = args.WebSession.Response;
if (args.WebSession.Request.CancelRequest)
if (request.CancelRequest)
{
//syphon out the request body from client before setting the new body
await args.SyphonOutBodyAsync(true);
await HandleHttpSessionResponse(args);
if (!response.KeepAlive)
{
break;
return;
}
continue;
......@@ -399,7 +404,7 @@ namespace Titanium.Web.Proxy
//create a new connection if hostname/upstream end point changes
if (connection != null
&& (!connection.HostName.Equals(args.WebSession.Request.RequestUri.Host, StringComparison.OrdinalIgnoreCase)
&& (!connection.HostName.Equals(request.RequestUri.Host, StringComparison.OrdinalIgnoreCase)
|| (args.WebSession.UpStreamEndPoint != null
&& !args.WebSession.UpStreamEndPoint.Equals(connection.UpStreamEndPoint))))
{
......@@ -413,12 +418,11 @@ namespace Titanium.Web.Proxy
}
//if upgrading to websocket then relay the requet without reading the contents
if (args.WebSession.Request.UpgradeToWebSocket)
if (request.UpgradeToWebSocket)
{
//prepare the prefix content
var requestHeaders = args.WebSession.Request.Headers;
await connection.StreamWriter.WriteLineAsync(httpCmd);
await connection.StreamWriter.WriteHeadersAsync(requestHeaders);
await connection.StreamWriter.WriteHeadersAsync(request.Headers);
string httpStatus = await connection.StreamReader.ReadLineAsync();
Response.ParseResponseLine(httpStatus, out var responseVersion, out int responseStatusCode,
......@@ -445,16 +449,22 @@ namespace Titanium.Web.Proxy
(buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); },
ExceptionFunc);
break;
return;
}
//construct the web request that we are going to issue on behalf of the client.
await HandleHttpSessionRequestInternal(connection, args);
if (args.WebSession.ServerConnection == null)
{
//server connection was closed
return;
}
//if connection is closing exit
if (!response.KeepAlive)
{
break;
return;
}
}
catch (Exception e) when (!(e is ProxyHttpException))
......@@ -493,6 +503,8 @@ namespace Titanium.Web.Proxy
var request = args.WebSession.Request;
request.Locked = true;
var body = request.CompressBodyAndUpdateContentLength();
//if expect continue is enabled then send the headers first
//and see if server would return 100 conitinue
if (request.ExpectContinue)
......@@ -531,25 +543,8 @@ namespace Titanium.Web.Proxy
//If request was modified by user
if (request.IsBodyRead)
{
bool isChunked = request.IsChunked;
string contentEncoding = request.ContentEncoding;
var body = request.Body;
if (contentEncoding != null && body != null)
{
body = GetCompressedBody(contentEncoding, body);
if (isChunked == false)
{
request.ContentLength = body.Length;
}
else
{
request.ContentLength = -1;
}
}
await args.WebSession.ServerConnection.StreamWriter.WriteBodyAsync(body, isChunked);
var writer = args.WebSession.ServerConnection.StreamWriter;
await writer.WriteBodyAsync(body, request.IsChunked);
}
else
{
......
......@@ -43,6 +43,29 @@ namespace Titanium.Web.Proxy
await InvokeBeforeResponse(args);
}
// it may changed in the user event
response = args.WebSession.Response;
var clientStreamWriter = args.ProxyClient.ClientStreamWriter;
if (response.TerminateResponse || response.Locked)
{
await clientStreamWriter.WriteResponseAsync(response);
if (!response.TerminateResponse)
{
//syphon out the response body from server before setting the new body
await args.SyphonOutBodyAsync(false);
}
else
{
args.WebSession.ServerConnection.Dispose();
args.WebSession.ServerConnection = null;
}
return;
}
//if user requested to send request again
//likely after making modifications from User Response Handler
if (args.ReRequest)
......@@ -55,8 +78,6 @@ namespace Titanium.Web.Proxy
response.Locked = true;
var clientStreamWriter = args.ProxyClient.ClientStreamWriter;
//Write back to client 100-conitinue response if that's what server returned
if (response.Is100Continue)
{
......@@ -69,9 +90,6 @@ namespace Titanium.Web.Proxy
await clientStreamWriter.WriteLineAsync();
}
//Write back response status to client
await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, response.StatusCode, response.StatusDescription);
if (!args.IsTransparent)
{
response.Headers.FixProxyHeaders();
......@@ -79,29 +97,12 @@ namespace Titanium.Web.Proxy
if (response.IsBodyRead)
{
bool isChunked = response.IsChunked;
string contentEncoding = response.ContentEncoding;
var body = response.Body;
if (contentEncoding != null && body != null)
{
body = GetCompressedBody(contentEncoding, body);
if (isChunked == false)
{
response.ContentLength = body.Length;
}
else
{
response.ContentLength = -1;
}
}
await clientStreamWriter.WriteHeadersAsync(response.Headers);
await clientStreamWriter.WriteBodyAsync(body, isChunked);
await clientStreamWriter.WriteResponseAsync(response);
}
else
{
//Write back response status to client
await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, response.StatusCode, response.StatusDescription);
await clientStreamWriter.WriteHeadersAsync(response.Headers);
//Write body if exists
......@@ -117,27 +118,6 @@ namespace Titanium.Web.Proxy
}
}
/// <summary>
/// get the compressed body from given bytes
/// </summary>
/// <param name="encodingType"></param>
/// <param name="body"></param>
/// <returns></returns>
private byte[] GetCompressedBody(string encodingType, byte[] body)
{
var compressor = CompressionFactory.GetCompression(encodingType);
using (var ms = new MemoryStream())
{
using (var zip = compressor.GetStream(ms))
{
zip.Write(body, 0, body.Length);
}
return ms.ToArray();
}
}
private async Task InvokeBeforeResponse(SessionEventArgs args)
{
if (BeforeResponse != null)
......
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