Unverified Commit 5e1841ff authored by honfika's avatar honfika Committed by GitHub

Merge pull request #390 from justcoding121/develop

merge to beta (again)
parents b27ba8cf fa0d9f2a
......@@ -5,6 +5,7 @@ using System.Net;
using System.Net.Security;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
......@@ -13,6 +14,8 @@ namespace Titanium.Web.Proxy.Examples.Basic
{
public class ProxyTestController
{
private readonly object lockObj = new object();
private readonly ProxyServer proxyServer;
private ExplicitProxyEndPoint explicitEndPoint;
......@@ -33,11 +36,28 @@ namespace Titanium.Web.Proxy.Examples.Basic
//generate root certificate without storing it in file system
//proxyServer.CertificateManager.CreateRootCertificate(false);
//proxyServer.CertificateManager.TrustRootCertificate();
//proxyServer.CertificateManager.TrustRootCertificateAsAdmin();
proxyServer.ExceptionFunc = exception => Console.WriteLine(exception.Message);
proxyServer.ExceptionFunc = exception =>
{
lock (lockObj)
{
var color = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;
if (exception is ProxyHttpException phex)
{
Console.WriteLine(exception.Message + ": " + phex.InnerException?.Message);
}
else
{
Console.WriteLine(exception.Message);
}
Console.ForegroundColor = color;
}
};
proxyServer.ForwardToUpstreamGateway = true;
proxyServer.CertificateManager.SaveFakeCertificates = true;
//optionally set the Certificate Engine
......@@ -52,7 +72,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
{
proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse;
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
......@@ -60,12 +80,12 @@ namespace Titanium.Web.Proxy.Examples.Basic
explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{
//You can set only one of the ExcludedHttpsHostNameRegex and IncludedHttpsHostNameRegex properties, otherwise ArgumentException will be thrown
//You can set only one of the ExcludedHttpsHostNameRegex and IncludedHttpsHostNameRegex properties, otherwise ArgumentException will be thrown
//Use self-issued generic certificate on all https requests
//Optimizes performance by not creating a certificate for each https-enabled domain
//Useful when certificate trust is not required by proxy clients
//GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password")
//Use self-issued generic certificate on all https requests
//Optimizes performance by not creating a certificate for each https-enabled domain
//Useful when certificate trust is not required by proxy clients
//GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password")
};
//Fired when a CONNECT request is received
......@@ -125,7 +145,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
private async Task OnBeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
{
string hostname = e.WebSession.Request.RequestUri.Host;
Console.WriteLine("Tunnel to: " + hostname);
WriteToConsole("Tunnel to: " + hostname);
if (hostname.Contains("dropbox.com"))
{
......@@ -143,8 +163,8 @@ namespace Titanium.Web.Proxy.Examples.Basic
//intecept & cancel redirect or update requests
private async Task OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine("Active Client Connections:" + ((ProxyServer)sender).ClientConnectionCount);
Console.WriteLine(e.WebSession.Request.Url);
WriteToConsole("Active Client Connections:" + ((ProxyServer)sender).ClientConnectionCount);
WriteToConsole(e.WebSession.Request.Url);
//read request headers
requestHeaderHistory[e.Id] = e.WebSession.Request.Headers;
......@@ -192,16 +212,16 @@ namespace Titanium.Web.Proxy.Examples.Basic
private void MultipartRequestPartSent(object sender, MultipartRequestPartSentEventArgs e)
{
var session = (SessionEventArgs)sender;
Console.WriteLine("Multipart form data headers:");
WriteToConsole("Multipart form data headers:");
foreach (var header in e.Headers)
{
Console.WriteLine(header);
WriteToConsole(header.ToString());
}
}
private async Task OnResponse(object sender, SessionEventArgs e)
{
Console.WriteLine("Active Server Connections:" + ((ProxyServer)sender).ServerConnectionCount);
WriteToConsole("Active Server Connections:" + ((ProxyServer)sender).ServerConnectionCount);
//if (requestBodyHistory.ContainsKey(e.Id))
//{
......@@ -213,7 +233,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
//responseHeaderHistory[e.Id] = e.WebSession.Response.Headers;
//// print out process id of current session
////Console.WriteLine($"PID: {e.WebSession.ProcessId.Value}");
////WriteToConsole($"PID: {e.WebSession.ProcessId.Value}");
////if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
//if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST")
......@@ -259,5 +279,13 @@ namespace Titanium.Web.Proxy.Examples.Basic
return Task.FromResult(0);
}
private void WriteToConsole(string message)
{
lock (lockObj)
{
Console.WriteLine(message);
}
}
}
}
......@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
......@@ -102,6 +103,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
proxyServer.BeforeRequest += ProxyServer_BeforeRequest;
proxyServer.BeforeResponse += ProxyServer_BeforeResponse;
proxyServer.AfterResponse += ProxyServer_AfterResponse;
explicitEndPoint.BeforeTunnelConnectRequest += ProxyServer_BeforeTunnelConnectRequest;
explicitEndPoint.BeforeTunnelConnectResponse += ProxyServer_BeforeTunnelConnectResponse;
proxyServer.ClientConnectionCountChanged += delegate { Dispatcher.Invoke(() => { ClientConnectionCount = proxyServer.ClientConnectionCount; }); };
......@@ -179,6 +181,17 @@ namespace Titanium.Web.Proxy.Examples.Wpf
}
}
private async Task ProxyServer_AfterResponse(object sender, SessionEventArgs e)
{
await Dispatcher.InvokeAsync(() =>
{
if (sessionDictionary.TryGetValue(e.WebSession, out var item))
{
item.Exception = e.Exception;
}
});
}
private SessionListItem AddSession(SessionEventArgs e)
{
var item = CreateSessionListItem(e);
......@@ -255,9 +268,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf
}
//string hexStr = string.Join(" ", data.Select(x => x.ToString("X2")));
TextBoxRequest.Text = request.HeaderText + request.Encoding.GetString(data) +
(truncated ? Environment.NewLine + $"Data is truncated after {truncateLimit} bytes" : null) +
(request as ConnectRequest)?.ClientHelloInfo;
var sb = new StringBuilder();
sb.Append(request.HeaderText);
sb.Append(request.Encoding.GetString(data));
sb.Append(truncated ? Environment.NewLine + $"Data is truncated after {truncateLimit} bytes" : null);
sb.Append((request as ConnectRequest)?.ClientHelloInfo);
TextBoxRequest.Text = sb.ToString();
var response = session.Response;
data = (response.IsBodyRead ? response.Body : null) ?? new byte[0];
......@@ -268,9 +284,18 @@ namespace Titanium.Web.Proxy.Examples.Wpf
}
//hexStr = string.Join(" ", data.Select(x => x.ToString("X2")));
TextBoxResponse.Text = session.Response.HeaderText + session.Response.Encoding.GetString(data) +
(truncated ? Environment.NewLine + $"Data is truncated after {truncateLimit} bytes" : null) +
(session.Response as ConnectResponse)?.ServerHelloInfo;
sb = new StringBuilder();
sb.Append(response.HeaderText);
sb.Append(response.Encoding.GetString(data));
sb.Append(truncated ? Environment.NewLine + $"Data is truncated after {truncateLimit} bytes" : null);
sb.Append((response as ConnectResponse)?.ServerHelloInfo);
if (SelectedSession.Exception != null)
{
sb.Append(Environment.NewLine);
sb.Append(SelectedSession.Exception);
}
TextBoxResponse.Text = sb.ToString();
}
}
}
......@@ -17,6 +17,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
private string process;
private long receivedDataCount;
private long sentDataCount;
private Exception exception;
public int Number { get; set; }
......@@ -72,6 +73,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf
set => SetField(ref sentDataCount, value);
}
public Exception Exception
{
get => exception;
set => SetField(ref exception, value);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void SetField<T>(ref T field, T value,[CallerMemberName] string propertyName = null)
......
......@@ -51,8 +51,8 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="StreamExtended, Version=1.0.138.0, Culture=neutral, PublicKeyToken=bbfa0f1d54f50043, processorArchitecture=MSIL">
<HintPath>..\..\packages\StreamExtended.1.0.138-beta\lib\net45\StreamExtended.dll</HintPath>
<Reference Include="StreamExtended, Version=1.0.141.0, Culture=neutral, PublicKeyToken=bbfa0f1d54f50043, processorArchitecture=MSIL">
<HintPath>..\..\packages\StreamExtended.1.0.141-beta\lib\net45\StreamExtended.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
......
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="StreamExtended" version="1.0.138-beta" targetFramework="net45" />
<package id="StreamExtended" version="1.0.141-beta" targetFramework="net45" />
</packages>
\ No newline at end of file
......@@ -8,7 +8,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Titanium.Web.Proxy.UnitTests</RootNamespace>
<AssemblyName>Titanium.Web.Proxy.UnitTests</AssemblyName>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
......@@ -16,6 +16,7 @@
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
......
using System;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Compression
{
/// <summary>
/// A factory to generate the compression methods based on the type of compression
/// </summary>
internal class CompressionFactory
internal static class CompressionFactory
{
//cache
private static Lazy<ICompression> gzip = new Lazy<ICompression>(() => new GZipCompression());
private static Lazy<ICompression> deflate = new Lazy<ICompression>(() => new DeflateCompression());
private static readonly Lazy<ICompression> gzip = new Lazy<ICompression>(() => new GZipCompression());
private static readonly Lazy<ICompression> deflate = new Lazy<ICompression>(() => new DeflateCompression());
public static ICompression GetCompression(string type)
{
switch (type)
{
case "gzip":
case KnownHeaders.ContentEncodingGzip:
return gzip.Value;
case "deflate":
case KnownHeaders.ContentEncodingDeflate:
return deflate.Value;
default:
return null;
throw new Exception($"Unsupported compression mode: {type}");
}
}
}
......
......@@ -9,17 +9,9 @@ namespace Titanium.Web.Proxy.Compression
/// </summary>
internal class DeflateCompression : ICompression
{
public async Task<byte[]> Compress(byte[] responseBody)
public Stream GetStream(Stream stream)
{
using (var ms = new MemoryStream())
{
using (var zip = new DeflateStream(ms, CompressionMode.Compress, true))
{
await zip.WriteAsync(responseBody, 0, responseBody.Length);
}
return ms.ToArray();
}
return new DeflateStream(stream, CompressionMode.Compress, true);
}
}
}
......@@ -9,17 +9,9 @@ namespace Titanium.Web.Proxy.Compression
/// </summary>
internal class GZipCompression : ICompression
{
public async Task<byte[]> Compress(byte[] responseBody)
public Stream GetStream(Stream stream)
{
using (var ms = new MemoryStream())
{
using (var zip = new GZipStream(ms, CompressionMode.Compress, true))
{
await zip.WriteAsync(responseBody, 0, responseBody.Length);
}
return ms.ToArray();
}
return new GZipStream(stream, CompressionMode.Compress, true);
}
}
}
using System.Threading.Tasks;
using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression
{
......@@ -7,6 +8,6 @@ namespace Titanium.Web.Proxy.Compression
/// </summary>
interface ICompression
{
Task<byte[]> Compress(byte[] responseBody);
Stream GetStream(Stream stream);
}
}
namespace Titanium.Web.Proxy.Decompression
using System;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Decompression
{
/// <summary>
/// A factory to generate the de-compression methods based on the type of compression
/// </summary>
internal class DecompressionFactory
{
internal IDecompression Create(string type)
//cache
private static readonly Lazy<IDecompression> gzip = new Lazy<IDecompression>(() => new GZipDecompression());
private static readonly Lazy<IDecompression> deflate = new Lazy<IDecompression>(() => new DeflateDecompression());
public static IDecompression Create(string type)
{
switch (type)
{
case "gzip":
return new GZipDecompression();
case "deflate":
return new DeflateDecompression();
case KnownHeaders.ContentEncodingGzip:
return gzip.Value;
case KnownHeaders.ContentEncodingDeflate:
return deflate.Value;
default:
return new DefaultDecompression();
throw new Exception($"Unsupported decompression mode: {type}");
}
}
}
......
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Decompression
{
/// <summary>
/// When no compression is specified just return the byte array
/// </summary>
internal class DefaultDecompression : IDecompression
{
public Task<byte[]> Decompress(byte[] compressedArray, int bufferSize)
{
return Task.FromResult(compressedArray);
}
}
}
......@@ -10,30 +10,9 @@ namespace Titanium.Web.Proxy.Decompression
/// </summary>
internal class DeflateDecompression : IDecompression
{
public async Task<byte[]> Decompress(byte[] compressedArray, int bufferSize)
public Stream GetStream(Stream stream)
{
using (var stream = new MemoryStream(compressedArray))
using (var decompressor = new DeflateStream(stream, CompressionMode.Decompress))
{
var buffer = BufferPool.GetBuffer(bufferSize);
try
{
using (var output = new MemoryStream())
{
int read;
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
output.Write(buffer, 0, read);
}
return output.ToArray();
}
}
finally
{
BufferPool.ReturnBuffer(buffer);
}
}
return new DeflateStream(stream, CompressionMode.Decompress, true);
}
}
}
......@@ -10,29 +10,9 @@ namespace Titanium.Web.Proxy.Decompression
/// </summary>
internal class GZipDecompression : IDecompression
{
public async Task<byte[]> Decompress(byte[] compressedArray, int bufferSize)
public Stream GetStream(Stream stream)
{
using (var decompressor = new GZipStream(new MemoryStream(compressedArray), CompressionMode.Decompress))
{
var buffer = BufferPool.GetBuffer(bufferSize);
try
{
using (var output = new MemoryStream())
{
int read;
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
output.Write(buffer, 0, read);
}
return output.ToArray();
}
}
finally
{
BufferPool.ReturnBuffer(buffer);
}
}
return new GZipStream(stream, CompressionMode.Decompress, true);
}
}
}
using System.Threading.Tasks;
using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Decompression
{
......@@ -7,6 +8,6 @@ namespace Titanium.Web.Proxy.Decompression
/// </summary>
internal interface IDecompression
{
Task<byte[]> Decompress(byte[] compressedArray, int bufferSize);
Stream GetStream(Stream stream);
}
}
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading.Tasks;
using StreamExtended.Helpers;
using StreamExtended.Network;
namespace Titanium.Web.Proxy.EventArguments
{
internal class LimitedStream : Stream
{
private readonly CustomBufferedStream baseStream;
private readonly CustomBinaryReader baseReader;
private readonly bool isChunked;
private bool readChunkTrail;
private long bytesRemaining;
public LimitedStream(CustomBufferedStream baseStream, CustomBinaryReader baseReader, bool isChunked, long contentLength)
{
this.baseStream = baseStream;
this.baseReader = baseReader;
this.isChunked = isChunked;
bytesRemaining = isChunked ? 0 : contentLength;
}
private void GetNextChunk()
{
if (readChunkTrail)
{
// read the chunk trail of the previous chunk
string s = baseReader.ReadLineAsync().Result;
}
readChunkTrail = true;
string chunkHead = baseReader.ReadLineAsync().Result;
int idx = chunkHead.IndexOf(";");
if (idx >= 0)
{
// remove chunk extension
chunkHead = chunkHead.Substring(0, idx);
}
int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
bytesRemaining = chunkSize;
if (chunkSize == 0)
{
bytesRemaining = -1;
//chunk trail
string s = baseReader.ReadLineAsync().Result;
}
}
public override void Flush()
{
throw new NotSupportedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
if (bytesRemaining == -1)
{
return 0;
}
if (bytesRemaining == 0)
{
if (isChunked)
{
GetNextChunk();
}
else
{
bytesRemaining = -1;
}
}
if (bytesRemaining == -1)
{
return 0;
}
int toRead = (int)Math.Min(count, bytesRemaining);
int res = baseStream.Read(buffer, offset, toRead);
bytesRemaining -= res;
return res;
}
public async Task Finish()
{
var buffer = BufferPool.GetBuffer(baseReader.Buffer.Length);
try
{
while (bytesRemaining != -1)
{
await ReadAsync(buffer, 0, buffer.Length);
}
}
finally
{
BufferPool.ReturnBuffer(buffer);
}
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => throw new NotSupportedException();
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
}
}
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Reflection;
using System.Threading.Tasks;
using StreamExtended.Helpers;
using StreamExtended.Network;
......@@ -100,6 +102,8 @@ namespace Titanium.Web.Proxy.EventArguments
public bool IsTransparent => LocalEndPoint is TransparentProxyEndPoint;
public Exception Exception { get; internal set; }
/// <summary>
/// Constructor to initialize the proxy
/// </summary>
......@@ -146,7 +150,7 @@ namespace Titanium.Web.Proxy.EventArguments
//If not already read (not cached yet)
if (!request.IsBodyRead)
{
var body = await ReadBodyAsync(ProxyClient.ClientStreamReader, request.IsChunked, request.ContentLength, request.ContentEncoding, true);
var body = await ReadBodyAsync(ProxyClient.ClientStreamReader, true);
request.Body = body;
//Now set the flag to true
......@@ -174,8 +178,7 @@ namespace Titanium.Web.Proxy.EventArguments
}
catch (Exception ex)
{
var ex2 = new Exception("Exception thrown in user event", ex);
exceptionFunc(ex2);
exceptionFunc(new Exception("Exception thrown in user event", ex));
}
}
......@@ -187,8 +190,7 @@ namespace Titanium.Web.Proxy.EventArguments
}
catch (Exception ex)
{
var ex2 = new Exception("Exception thrown in user event", ex);
exceptionFunc(ex2);
exceptionFunc(new Exception("Exception thrown in user event", ex));
}
}
......@@ -200,8 +202,7 @@ namespace Titanium.Web.Proxy.EventArguments
}
catch (Exception ex)
{
var ex2 = new Exception("Exception thrown in user event", ex);
exceptionFunc(ex2);
exceptionFunc(new Exception("Exception thrown in user event", ex));
}
}
......@@ -210,7 +211,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
private async Task ReadResponseBodyAsync()
{
if (!WebSession.Request.RequestLocked)
if (!WebSession.Request.Locked)
{
throw new Exception("You cannot read the response body before request is made to server.");
}
......@@ -224,7 +225,7 @@ namespace Titanium.Web.Proxy.EventArguments
//If not already read (not cached yet)
if (!response.IsBodyRead)
{
var body = await ReadBodyAsync(WebSession.ServerConnection.StreamReader, response.IsChunked, response.ContentLength, response.ContentEncoding, false);
var body = await ReadBodyAsync(WebSession.ServerConnection.StreamReader, false);
response.Body = body;
//Now set the flag to true
......@@ -234,21 +235,21 @@ namespace Titanium.Web.Proxy.EventArguments
}
}
private async Task<byte[]> ReadBodyAsync(CustomBinaryReader reader, bool isChunked, long contentLength, string contentEncoding, bool isRequest)
private async Task<byte[]> ReadBodyAsync(CustomBinaryReader reader, bool isRequest)
{
using (var bodyStream = new MemoryStream())
{
var writer = new HttpWriter(bodyStream, bufferSize);
if (isRequest)
{
await CopyRequestBodyAsync(writer, true);
await CopyRequestBodyAsync(writer, TransformationMode.Uncompress);
}
else
{
await CopyResponseBodyAsync(writer, true);
await CopyResponseBodyAsync(writer, TransformationMode.Uncompress);
}
return await GetDecompressedBodyAsync(contentEncoding, bodyStream.ToArray());
return bodyStream.ToArray();
}
}
......@@ -256,7 +257,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// This is called when the request is PUT/POST/PATCH to read the body
/// </summary>
/// <returns></returns>
internal async Task CopyRequestBodyAsync(HttpWriter writer, bool removeChunkedEncoding)
internal async Task CopyRequestBodyAsync(HttpWriter writer, TransformationMode transformation)
{
// End the operation
var request = WebSession.Request;
......@@ -293,16 +294,56 @@ namespace Titanium.Web.Proxy.EventArguments
}
else
{
await writer.CopyBodyAsync(reader, request.IsChunked, contentLength, removeChunkedEncoding);
await CopyBodyAsync(ProxyClient.ClientStream, reader, writer, request, transformation, OnDataSent);
}
}
internal async Task CopyResponseBodyAsync(HttpWriter writer, bool removeChunkedEncoding)
internal async Task CopyResponseBodyAsync(HttpWriter writer, TransformationMode transformation)
{
var response = WebSession.Response;
var reader = WebSession.ServerConnection.StreamReader;
await writer.CopyBodyAsync(reader, response.IsChunked, response.ContentLength, removeChunkedEncoding);
await CopyBodyAsync(WebSession.ServerConnection.Stream, reader, writer, response, transformation, OnDataReceived);
}
private async Task CopyBodyAsync(CustomBufferedStream stream, CustomBinaryReader reader, HttpWriter writer,
RequestResponseBase requestResponse, TransformationMode transformation, Action<byte[], int, int> onCopy)
{
bool isChunked = requestResponse.IsChunked;
long contentLength = requestResponse.ContentLength;
if (transformation == TransformationMode.None)
{
await writer.CopyBodyAsync(reader, isChunked, contentLength, onCopy);
return;
}
LimitedStream limitedStream;
Stream decompressStream = null;
string contentEncoding = requestResponse.ContentEncoding;
Stream s = limitedStream = new LimitedStream(stream, reader, isChunked, contentLength);
if (transformation == TransformationMode.Uncompress && contentEncoding != null)
{
s = decompressStream = DecompressionFactory.Create(contentEncoding).GetStream(s);
}
try
{
var bufStream = new CustomBufferedStream(s, bufferSize, true);
reader = new CustomBinaryReader(bufStream, bufferSize);
await writer.CopyBodyAsync(reader, false, -1, onCopy);
}
finally
{
reader?.Dispose();
decompressStream?.Dispose();
await limitedStream.Finish();
limitedStream.Dispose();
}
}
/// <summary>
......@@ -401,19 +442,20 @@ namespace Titanium.Web.Proxy.EventArguments
/// <param name="body"></param>
public async Task SetRequestBody(byte[] body)
{
if (WebSession.Request.RequestLocked)
var request = WebSession.Request;
if (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)
if (!request.IsBodyRead)
{
await ReadRequestBodyAsync();
}
WebSession.Request.Body = body;
WebSession.Request.ContentLength = WebSession.Request.IsChunked ? -1 : body.Length;
request.Body = body;
request.UpdateContentLength();
}
/// <summary>
......@@ -422,7 +464,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// <param name="body"></param>
public async Task SetRequestBodyString(string body)
{
if (WebSession.Request.RequestLocked)
if (WebSession.Request.Locked)
{
throw new Exception("You cannot call this function after request is made to server.");
}
......@@ -470,28 +512,23 @@ namespace Titanium.Web.Proxy.EventArguments
/// <param name="body"></param>
public async Task SetResponseBody(byte[] body)
{
if (!WebSession.Request.RequestLocked)
if (!WebSession.Request.Locked)
{
throw new Exception("You cannot call this function before request is made to server.");
}
var response = WebSession.Response;
//syphon out the response body from server before setting the new body
if (WebSession.Response.Body == null)
if (response.Body == null)
{
await GetResponseBody();
}
WebSession.Response.Body = body;
response.Body = 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;
}
response.UpdateContentLength();
}
/// <summary>
......@@ -500,7 +537,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// <param name="body"></param>
public async Task SetResponseBodyString(string body)
{
if (!WebSession.Request.RequestLocked)
if (!WebSession.Request.Locked)
{
throw new Exception("You cannot call this function before request is made to server.");
}
......@@ -516,14 +553,6 @@ namespace Titanium.Web.Proxy.EventArguments
await SetResponseBody(bodyBytes);
}
private async Task<byte[]> GetDecompressedBodyAsync(string encodingType, byte[] bodyStream)
{
var decompressionFactory = new DecompressionFactory();
var decompressor = decompressionFactory.Create(encodingType);
return await decompressor.Decompress(bodyStream, bufferSize);
}
/// <summary>
/// Before request is made to server
/// Respond with the specified HTML string to client
......@@ -620,14 +649,14 @@ namespace Titanium.Web.Proxy.EventArguments
/// a generic responder method
public void Respond(Response response)
{
if (WebSession.Request.RequestLocked)
if (WebSession.Request.Locked)
{
throw new Exception("You cannot call this function after request is made to server.");
}
WebSession.Request.RequestLocked = true;
WebSession.Request.Locked = true;
response.ResponseLocked = true;
response.Locked = true;
response.IsBodyRead = true;
WebSession.Response = response;
......@@ -645,6 +674,7 @@ namespace Titanium.Web.Proxy.EventArguments
DataSent = null;
DataReceived = null;
MultipartRequestPartSent = null;
Exception = null;
WebSession.FinishSession();
}
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.EventArguments
{
internal enum TransformationMode
{
None,
/// <summary>
/// Removes the chunked encoding
/// </summary>
RemoveChunked,
/// <summary>
/// Uncompress the body (this also removes the chunked encoding if exists)
/// </summary>
Uncompress,
}
}
......@@ -5,6 +5,7 @@ using System.Text;
using System.Threading.Tasks;
using StreamExtended.Helpers;
using StreamExtended.Network;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Shared;
......@@ -152,26 +153,26 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="streamReader"></param>
/// <param name="isChunked"></param>
/// <param name="contentLength"></param>
/// <param name="removeChunkedEncoding"></param>
/// <param name="onCopy"></param>
/// <returns></returns>
internal Task CopyBodyAsync(CustomBinaryReader streamReader, bool isChunked, long contentLength, bool removeChunkedEncoding)
internal Task CopyBodyAsync(CustomBinaryReader streamReader, bool isChunked, long contentLength, Action<byte[], int, int> onCopy)
{
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if (isChunked)
{
//Need to revist, find any potential bugs
//send the body bytes to server in chunks
return CopyBodyChunkedAsync(streamReader, removeChunkedEncoding);
return CopyBodyChunkedAsync(streamReader, onCopy);
}
//http 1.0
//http 1.0 or the stream reader limits the stream
if (contentLength == -1)
{
contentLength = long.MaxValue;
}
//If not chunked then its easy just read the amount of bytes mentioned in content length header
return CopyBytesFromStream(streamReader, contentLength);
return CopyBytesFromStream(streamReader, contentLength, onCopy);
}
/// <summary>
......@@ -196,29 +197,30 @@ namespace Titanium.Web.Proxy.Helpers
/// Copies the streams chunked
/// </summary>
/// <param name="reader"></param>
/// <param name="removeChunkedEncoding"></param>
/// <param name="onCopy"></param>
/// <returns></returns>
private async Task CopyBodyChunkedAsync(CustomBinaryReader reader, bool removeChunkedEncoding)
private async Task CopyBodyChunkedAsync(CustomBinaryReader reader, Action<byte[], int, int> onCopy)
{
while (true)
{
string chunkHead = await reader.ReadLineAsync();
int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
if (!removeChunkedEncoding)
int idx = chunkHead.IndexOf(";");
if (idx >= 0)
{
await WriteLineAsync(chunkHead);
// remove chunk extension
chunkHead = chunkHead.Substring(0, idx);
}
int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
await WriteLineAsync(chunkHead);
if (chunkSize != 0)
{
await CopyBytesFromStream(reader, chunkSize);
await CopyBytesFromStream(reader, chunkSize, onCopy);
}
if (!removeChunkedEncoding)
{
await WriteLineAsync();
}
await WriteLineAsync();
//chunk trail
await reader.ReadLineAsync();
......@@ -235,8 +237,9 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary>
/// <param name="reader"></param>
/// <param name="count"></param>
/// <param name="onCopy"></param>
/// <returns></returns>
private async Task CopyBytesFromStream(CustomBinaryReader reader, long count)
private async Task CopyBytesFromStream(CustomBinaryReader reader, long count, Action<byte[], int, int> onCopy)
{
var buffer = reader.Buffer;
long remainingBytes = count;
......@@ -258,6 +261,8 @@ namespace Titanium.Web.Proxy.Helpers
remainingBytes -= bytesRead;
await WriteAsync(buffer, 0, bytesRead);
onCopy?.Invoke(buffer, 0, bytesRead);
}
}
}
......
......@@ -46,7 +46,7 @@ namespace Titanium.Web.Proxy.Helpers
protocol = ProxyServer.UriSchemeHttp;
break;
case ProxyProtocolType.Https:
protocol = Proxy.ProxyServer.UriSchemeHttps;
protocol = ProxyServer.UriSchemeHttps;
break;
default:
throw new Exception("Unsupported protocol type");
......
......@@ -22,7 +22,6 @@ namespace Titanium.Web.Proxy.Http
StatusDescription = "Connection Established"
};
response.Headers.AddHeader(KnownHeaders.Timestamp, DateTime.Now.ToString());
return response;
}
}
......
......@@ -40,6 +40,8 @@ namespace Titanium.Web.Proxy.Http
// Response headers
public const string ContentEncoding = "content-encoding";
public const string ContentEncodingDeflate = "deflate";
public const string ContentEncodingGzip = "gzip";
public const string Location = "Location";
......@@ -47,8 +49,5 @@ namespace Titanium.Web.Proxy.Http
public const string TransferEncoding = "transfer-encoding";
public const string TransferEncodingChunked = "chunked";
// ???
public const string Timestamp = "Timestamp";
}
}
......@@ -3,28 +3,17 @@ using System.ComponentModel;
using System.Text;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Http
{
/// <summary>
/// A HTTP(S) request object
/// Http(s) request object
/// </summary>
[TypeConverter(typeof(ExpandableObjectConverter))]
public class Request
public class Request : RequestResponseBase
{
/// <summary>
/// Cached request body as byte array
/// </summary>
private byte[] body;
/// <summary>
/// Cached request body as string
/// </summary>
private string bodyString;
/// <summary>
/// Request Method
/// </summary>
......@@ -45,16 +34,6 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
public string OriginalUrl { get; set; }
/// <summary>
/// Request Http Version
/// </summary>
public Version HttpVersion { get; set; }
/// <summary>
/// Keeps the request body data after the session is finished
/// </summary>
public bool KeepBody { get; set; }
/// <summary>
/// Has request body?
/// </summary>
......@@ -97,80 +76,6 @@ namespace Titanium.Web.Proxy.Http
set => Headers.SetOrAddHeaderValue(KnownHeaders.Host, value);
}
/// <summary>
/// Content encoding header value
/// </summary>
public string ContentEncoding => Headers.GetHeaderValueOrNull(KnownHeaders.ContentEncoding);
/// <summary>
/// Request content-length
/// </summary>
public long ContentLength
{
get
{
string headerValue = Headers.GetHeaderValueOrNull(KnownHeaders.ContentLength);
if (headerValue == null)
{
return -1;
}
long.TryParse(headerValue, out long contentLen);
if (contentLen >= 0)
{
return contentLen;
}
return -1;
}
set
{
if (value >= 0)
{
Headers.SetOrAddHeaderValue(KnownHeaders.ContentLength, value.ToString());
IsChunked = false;
}
else
{
Headers.RemoveHeader(KnownHeaders.ContentLength);
}
}
}
/// <summary>
/// Request content-type
/// </summary>
public string ContentType
{
get => Headers.GetHeaderValueOrNull(KnownHeaders.ContentType);
set => Headers.SetOrAddHeaderValue(KnownHeaders.ContentType, value);
}
/// <summary>
/// Is request body send as chunked bytes
/// </summary>
public bool IsChunked
{
get
{
string headerValue = Headers.GetHeaderValueOrNull(KnownHeaders.TransferEncoding);
return headerValue != null && headerValue.ContainsIgnoreCase(KnownHeaders.TransferEncodingChunked);
}
set
{
if (value)
{
Headers.SetOrAddHeaderValue(KnownHeaders.TransferEncoding, KnownHeaders.TransferEncodingChunked);
ContentLength = -1;
}
else
{
Headers.RemoveHeader(KnownHeaders.TransferEncoding);
}
}
}
/// <summary>
/// Does this request has a 100-continue header?
/// </summary>
......@@ -190,17 +95,12 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
public string Url => RequestUri.OriginalString;
/// <summary>
/// Encoding for this request
/// </summary>
public Encoding Encoding => HttpHelper.GetEncodingFromContentType(ContentType);
/// <summary>
/// Terminates the underlying Tcp Connection to client after current request
/// </summary>
internal bool CancelRequest { get; set; }
internal void EnsureBodyAvailable(bool throwWhenNotReadYet = true)
internal override void EnsureBodyAvailable(bool throwWhenNotReadYet = true)
{
//GET request don't have a request body to read
if (!HasBody)
......@@ -211,7 +111,7 @@ namespace Titanium.Web.Proxy.Http
if (!IsBodyRead)
{
if (RequestLocked)
if (Locked)
{
throw new Exception("You cannot get the request body after request is made to server.");
}
......@@ -225,41 +125,6 @@ namespace Titanium.Web.Proxy.Http
}
}
/// <summary>
/// Request body as byte array
/// </summary>
[Browsable(false)]
public byte[] Body
{
get
{
EnsureBodyAvailable();
return body;
}
internal set
{
body = value;
bodyString = null;
}
}
/// <summary>
/// Request body as string
/// Use the encoding specified in request to decode the byte[] data to string
/// </summary>
[Browsable(false)]
public string BodyString => bodyString ?? (bodyString = Encoding.GetString(Body));
/// <summary>
/// Request body was read by user?
/// </summary>
public bool IsBodyRead { get; internal set; }
/// <summary>
/// Request is ready to be sent (user callbacks are complete?)
/// </summary>
internal bool RequestLocked { get; set; }
/// <summary>
/// Does this request has an upgrade to websocket header?
/// </summary>
......@@ -278,11 +143,6 @@ namespace Titanium.Web.Proxy.Http
}
}
/// <summary>
/// Request header collection
/// </summary>
public HeaderCollection Headers { get; } = new HeaderCollection();
/// <summary>
/// Does server responsed positively for 100 continue request
/// </summary>
......@@ -296,7 +156,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Gets the header text.
/// </summary>
public string HeaderText
public override string HeaderText
{
get
{
......@@ -367,22 +227,5 @@ namespace Titanium.Web.Proxy.Http
return true;
}
/// <summary>
/// Finish the session
/// </summary>
public void FinishSession()
{
if (!KeepBody)
{
body = null;
bodyString = null;
}
}
public override string ToString()
{
return HeaderText;
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Http
{
public abstract class RequestResponseBase
{
/// <summary>
/// Cached body content as byte array
/// </summary>
private byte[] body;
/// <summary>
/// Cached body as string
/// </summary>
private string bodyString;
/// <summary>
/// Keeps the body data after the session is finished
/// </summary>
public bool KeepBody { get; set; }
/// <summary>
/// Http Version
/// </summary>
public Version HttpVersion { get; set; } = HttpHeader.VersionUnknown;
/// <summary>
/// Collection of all headers
/// </summary>
public HeaderCollection Headers { get; } = new HeaderCollection();
/// <summary>
/// Length of the body
/// </summary>
public long ContentLength
{
get
{
string headerValue = Headers.GetHeaderValueOrNull(KnownHeaders.ContentLength);
if (headerValue == null)
{
return -1;
}
if (long.TryParse(headerValue, out long contentLen) && contentLen >= 0)
{
return contentLen;
}
return -1;
}
set
{
if (value >= 0)
{
Headers.SetOrAddHeaderValue(KnownHeaders.ContentLength, value.ToString());
IsChunked = false;
}
else
{
Headers.RemoveHeader(KnownHeaders.ContentLength);
}
}
}
/// <summary>
/// Content encoding for this request/response
/// </summary>
public string ContentEncoding => Headers.GetHeaderValueOrNull(KnownHeaders.ContentEncoding)?.Trim();
/// <summary>
/// Encoding for this request/response
/// </summary>
public Encoding Encoding => HttpHelper.GetEncodingFromContentType(ContentType);
/// <summary>
/// Content-type of the request/response
/// </summary>
public string ContentType
{
get => Headers.GetHeaderValueOrNull(KnownHeaders.ContentType);
set => Headers.SetOrAddHeaderValue(KnownHeaders.ContentType, value);
}
/// <summary>
/// Is body send as chunked bytes
/// </summary>
public bool IsChunked
{
get
{
string headerValue = Headers.GetHeaderValueOrNull(KnownHeaders.TransferEncoding);
return headerValue != null && headerValue.ContainsIgnoreCase(KnownHeaders.TransferEncodingChunked);
}
set
{
if (value)
{
Headers.SetOrAddHeaderValue(KnownHeaders.TransferEncoding, KnownHeaders.TransferEncodingChunked);
ContentLength = -1;
}
else
{
Headers.RemoveHeader(KnownHeaders.TransferEncoding);
}
}
}
public abstract string HeaderText { get; }
/// <summary>
/// Body as byte array
/// </summary>
[Browsable(false)]
public byte[] Body
{
get
{
EnsureBodyAvailable();
return body;
}
internal set
{
body = value;
bodyString = null;
}
}
internal abstract void EnsureBodyAvailable(bool throwWhenNotReadYet = true);
/// <summary>
/// Body as string
/// Use the encoding specified to decode the byte[] data to string
/// </summary>
[Browsable(false)]
public string BodyString => bodyString ?? (bodyString = Encoding.GetString(Body));
/// <summary>
/// Was the body read by user?
/// </summary>
public bool IsBodyRead { get; internal set; }
/// <summary>
/// Is the request/response no more modifyable by user (user callbacks complete?)
/// </summary>
internal bool Locked { get; set; }
internal void UpdateContentLength()
{
ContentLength = IsChunked ? -1 : body.Length;
}
/// <summary>
/// Finish the session
/// </summary>
internal void FinishSession()
{
if (!KeepBody)
{
body = null;
bodyString = null;
}
}
public override string ToString()
{
return HeaderText;
}
}
}
......@@ -2,7 +2,6 @@
using System.ComponentModel;
using System.Text;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
......@@ -12,18 +11,8 @@ namespace Titanium.Web.Proxy.Http
/// Http(s) response object
/// </summary>
[TypeConverter(typeof(ExpandableObjectConverter))]
public class Response
public class Response : RequestResponseBase
{
/// <summary>
/// Cached response body content as byte array
/// </summary>
private byte[] body;
/// <summary>
/// Cached response body as string
/// </summary>
private string bodyString;
/// <summary>
/// Response Status Code.
/// </summary>
......@@ -34,26 +23,6 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
public string StatusDescription { get; set; }
/// <summary>
/// Encoding used in response
/// </summary>
public Encoding Encoding => HttpHelper.GetEncodingFromContentType(ContentType);
/// <summary>
/// Content encoding for this response
/// </summary>
public string ContentEncoding => Headers.GetHeaderValueOrNull(KnownHeaders.ContentEncoding)?.Trim();
/// <summary>
/// Http version
/// </summary>
public Version HttpVersion { get; set; } = HttpHeader.VersionUnknown;
/// <summary>
/// Keeps the response body data after the session is finished
/// </summary>
public bool KeepBody { get; set; }
/// <summary>
/// Has response body?
/// </summary>
......@@ -108,78 +77,9 @@ namespace Titanium.Web.Proxy.Http
}
}
/// <summary>
/// Content type of this response
/// </summary>
public string ContentType => Headers.GetHeaderValueOrNull(KnownHeaders.ContentType);
/// <summary>
/// Length of response body
/// </summary>
public long ContentLength
{
get
{
string headerValue = Headers.GetHeaderValueOrNull(KnownHeaders.ContentLength);
if (headerValue == null)
{
return -1;
}
if (long.TryParse(headerValue, out long contentLen))
{
return contentLen;
}
return -1;
}
set
{
if (value >= 0)
{
Headers.SetOrAddHeaderValue(KnownHeaders.ContentLength, value.ToString());
IsChunked = false;
}
else
{
Headers.RemoveHeader(KnownHeaders.ContentLength);
}
}
}
/// <summary>
/// Response transfer-encoding is chunked?
/// </summary>
public bool IsChunked
{
get
{
string headerValue = Headers.GetHeaderValueOrNull(KnownHeaders.TransferEncoding);
return headerValue != null && headerValue.ContainsIgnoreCase(KnownHeaders.TransferEncodingChunked);
}
set
{
if (value)
{
Headers.SetOrAddHeaderValue(KnownHeaders.TransferEncoding, KnownHeaders.TransferEncodingChunked);
ContentLength = -1;
}
else
{
Headers.RemoveHeader(KnownHeaders.TransferEncoding);
}
}
}
/// <summary>
/// Collection of all response headers
/// </summary>
public HeaderCollection Headers { get; } = new HeaderCollection();
internal void EnsureBodyAvailable()
internal override void EnsureBodyAvailable(bool throwWhenNotReadYet = true)
{
if (!IsBodyRead)
if (!IsBodyRead && throwWhenNotReadYet)
{
throw new Exception("Response body is not read yet. " +
"Use SessionEventArgs.GetResponseBody() or SessionEventArgs.GetResponseBodyAsString() " +
......@@ -187,41 +87,6 @@ namespace Titanium.Web.Proxy.Http
}
}
/// <summary>
/// Response body as byte array
/// </summary>
[Browsable(false)]
public byte[] Body
{
get
{
EnsureBodyAvailable();
return body;
}
internal set
{
body = value;
bodyString = null;
}
}
/// <summary>
/// Response body as string
/// Use the encoding specified in response to decode the byte[] data to string
/// </summary>
[Browsable(false)]
public string BodyString => bodyString ?? (bodyString = Encoding.GetString(Body));
/// <summary>
/// Was response body read by user
/// </summary>
public bool IsBodyRead { get; internal set; }
/// <summary>
/// Is response is no more modifyable by user (user callbacks complete?)
/// </summary>
internal bool ResponseLocked { get; set; }
/// <summary>
/// Is response 100-continue
/// </summary>
......@@ -240,7 +105,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Gets the header text.
/// </summary>
public string HeaderText
public override string HeaderText
{
get
{
......@@ -280,22 +145,5 @@ namespace Titanium.Web.Proxy.Http
statusCode = int.Parse(httpResult[1]);
statusDescription = httpResult[2];
}
/// <summary>
/// Finish the session
/// </summary>
internal void FinishSession()
{
if (!KeepBody)
{
body = null;
bodyString = null;
}
}
public override string ToString()
{
return HeaderText;
}
}
}
......@@ -233,7 +233,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
}
catch (Exception ex)
{
exceptionFunc.Invoke(new Exception("Failed to create BC certificate", ex));
exceptionFunc(new Exception("Failed to create BC certificate", ex));
}
if (!cancellationToken.IsCancellationRequested)
......
......@@ -271,7 +271,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
}
catch (Exception ex)
{
exceptionFunc.Invoke(new Exception("Failed to create Win certificate", ex));
exceptionFunc(new Exception("Failed to create Win certificate", ex));
}
if (!cancellationToken.IsCancellationRequested)
......
......@@ -189,6 +189,11 @@ namespace Titanium.Web.Proxy
/// </summary>
public event AsyncEventHandler<SessionEventArgs> BeforeResponse;
/// <summary>
/// Intercept after response from server
/// </summary>
public event AsyncEventHandler<SessionEventArgs> AfterResponse;
/// <summary>
/// Callback for error events in proxy
/// </summary>
......
......@@ -308,158 +308,168 @@ namespace Titanium.Web.Proxy
try
{
Request.ParseRequestLine(httpCmd, out string httpMethod, out string httpUrl, out var version);
try
{
Request.ParseRequestLine(httpCmd, out string httpMethod, out string httpUrl, out var version);
//Read the request headers in to unique and non-unique header collections
await HeaderParser.ReadHeaders(clientStreamReader, args.WebSession.Request.Headers);
//Read the request headers in to unique and non-unique header collections
await HeaderParser.ReadHeaders(clientStreamReader, args.WebSession.Request.Headers);
Uri httpRemoteUri;
if (uriSchemeRegex.IsMatch(httpUrl))
{
try
Uri httpRemoteUri;
if (uriSchemeRegex.IsMatch(httpUrl))
{
httpRemoteUri = new Uri(httpUrl);
try
{
httpRemoteUri = new Uri(httpUrl);
}
catch (Exception ex)
{
throw new Exception($"Invalid URI: '{httpUrl}'", ex);
}
}
catch (Exception ex)
else
{
throw new Exception($"Invalid URI: '{httpUrl}'", ex);
string host = args.WebSession.Request.Host ?? httpsConnectHostname;
string hostAndPath = host;
if (httpUrl.StartsWith("/"))
{
hostAndPath += httpUrl;
}
string url = string.Concat(httpsConnectHostname == null ? "http://" : "https://", hostAndPath);
try
{
httpRemoteUri = new Uri(url);
}
catch (Exception ex)
{
throw new Exception($"Invalid URI: '{url}'", ex);
}
}
}
else
{
string host = args.WebSession.Request.Host ?? httpsConnectHostname;
string hostAndPath = host;
if (httpUrl.StartsWith("/"))
args.WebSession.Request.RequestUri = httpRemoteUri;
args.WebSession.Request.OriginalUrl = httpUrl;
args.WebSession.Request.Method = httpMethod;
args.WebSession.Request.HttpVersion = version;
args.ProxyClient.ClientStream = clientStream;
args.ProxyClient.ClientStreamReader = clientStreamReader;
args.ProxyClient.ClientStreamWriter = clientStreamWriter;
//proxy authorization check
if (!args.IsTransparent && httpsConnectHostname == null && await CheckAuthorization(args) == false)
{
hostAndPath += httpUrl;
await InvokeBeforeResponse(args);
//send the response
await clientStreamWriter.WriteResponseAsync(args.WebSession.Response);
break;
}
string url = string.Concat(httpsConnectHostname == null ? "http://" : "https://", hostAndPath);
try
if (!isTransparentEndPoint)
{
httpRemoteUri = new Uri(url);
PrepareRequestHeaders(args.WebSession.Request.Headers);
args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority;
}
catch (Exception ex)
//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)
{
throw new Exception($"Invalid URI: '{url}'", ex);
await args.GetRequestBody();
}
}
args.WebSession.Request.RequestUri = httpRemoteUri;
args.WebSession.Request.OriginalUrl = httpUrl;
args.WebSession.Request.Method = httpMethod;
args.WebSession.Request.HttpVersion = version;
args.ProxyClient.ClientStream = clientStream;
args.ProxyClient.ClientStreamReader = clientStreamReader;
args.ProxyClient.ClientStreamWriter = clientStreamWriter;
//proxy authorization check
if (!args.IsTransparent && httpsConnectHostname == null && await CheckAuthorization(args) == false)
{
await InvokeBeforeResponse(args);
//send the response
await clientStreamWriter.WriteResponseAsync(args.WebSession.Response);
break;
}
//If user requested interception do it
await InvokeBeforeRequest(args);
if (!isTransparentEndPoint)
{
PrepareRequestHeaders(args.WebSession.Request.Headers);
args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority;
}
var response = args.WebSession.Response;
//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)
{
await args.GetRequestBody();
}
if (args.WebSession.Request.CancelRequest)
{
await HandleHttpSessionResponse(args);
//If user requested interception do it
await InvokeBeforeRequest(args);
if (!response.KeepAlive)
{
break;
}
var response = args.WebSession.Response;
continue;
}
if (args.WebSession.Request.CancelRequest)
{
await HandleHttpSessionResponse(args);
//create a new connection if hostname/upstream end point changes
if (connection != null
&& (!connection.HostName.Equals(args.WebSession.Request.RequestUri.Host, StringComparison.OrdinalIgnoreCase)
|| (args.WebSession.UpStreamEndPoint != null
&& !args.WebSession.UpStreamEndPoint.Equals(connection.UpStreamEndPoint))))
{
connection.Dispose();
connection = null;
}
if (!response.KeepAlive)
if (connection == null)
{
break;
connection = await GetServerConnection(args, false);
}
continue;
}
//if upgrading to websocket then relay the requet without reading the contents
if (args.WebSession.Request.UpgradeToWebSocket)
{
//prepare the prefix content
var requestHeaders = args.WebSession.Request.Headers;
await connection.StreamWriter.WriteLineAsync(httpCmd);
await connection.StreamWriter.WriteHeadersAsync(requestHeaders);
string httpStatus = await connection.StreamReader.ReadLineAsync();
//create a new connection if hostname/upstream end point changes
if (connection != null
&& (!connection.HostName.Equals(args.WebSession.Request.RequestUri.Host, StringComparison.OrdinalIgnoreCase)
|| (args.WebSession.UpStreamEndPoint != null
&& !args.WebSession.UpStreamEndPoint.Equals(connection.UpStreamEndPoint))))
{
connection.Dispose();
connection = null;
}
Response.ParseResponseLine(httpStatus, out var responseVersion, out int responseStatusCode,
out string responseStatusDescription);
response.HttpVersion = responseVersion;
response.StatusCode = responseStatusCode;
response.StatusDescription = responseStatusDescription;
if (connection == null)
{
connection = await GetServerConnection(args, false);
}
await HeaderParser.ReadHeaders(connection.StreamReader, response.Headers);
//if upgrading to websocket then relay the requet without reading the contents
if (args.WebSession.Request.UpgradeToWebSocket)
{
//prepare the prefix content
var requestHeaders = args.WebSession.Request.Headers;
await connection.StreamWriter.WriteLineAsync(httpCmd);
await connection.StreamWriter.WriteHeadersAsync(requestHeaders);
string httpStatus = await connection.StreamReader.ReadLineAsync();
if (!args.IsTransparent)
{
await clientStreamWriter.WriteResponseAsync(response);
}
Response.ParseResponseLine(httpStatus, out var responseVersion, out int responseStatusCode, out string responseStatusDescription);
response.HttpVersion = responseVersion;
response.StatusCode = responseStatusCode;
response.StatusDescription = responseStatusDescription;
//If user requested call back then do it
if (!args.WebSession.Response.Locked)
{
await InvokeBeforeResponse(args);
}
await HeaderParser.ReadHeaders(connection.StreamReader, response.Headers);
await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize,
(buffer, offset, count) => { args.OnDataSent(buffer, offset, count); },
(buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); },
ExceptionFunc);
if (!args.IsTransparent)
{
await clientStreamWriter.WriteResponseAsync(response);
break;
}
//If user requested call back then do it
if (!args.WebSession.Response.ResponseLocked)
//construct the web request that we are going to issue on behalf of the client.
await HandleHttpSessionRequestInternal(connection, args);
//if connection is closing exit
if (!response.KeepAlive)
{
await InvokeBeforeResponse(args);
break;
}
await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize,
(buffer, offset, count) => { args.OnDataSent(buffer, offset, count); },
(buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); },
ExceptionFunc);
break;
}
//construct the web request that we are going to issue on behalf of the client.
await HandleHttpSessionRequestInternal(connection, args);
//if connection is closing exit
if (!response.KeepAlive)
catch (Exception e) when (!(e is ProxyHttpException))
{
break;
throw new ProxyHttpException("Error occured whilst handling session request", e, args);
}
}
catch (Exception e) when (!(e is ProxyHttpException))
catch (Exception e)
{
throw new ProxyHttpException("Error occured whilst handling session request", e, args);
args.Exception = e;
throw;
}
finally
{
await InvokeAfterResponse(args);
args.Dispose();
}
}
......@@ -481,7 +491,7 @@ namespace Titanium.Web.Proxy
try
{
var request = args.WebSession.Request;
request.RequestLocked = true;
request.Locked = true;
//if expect continue is enabled then send the headers first
//and see if server would return 100 conitinue
......@@ -521,17 +531,25 @@ namespace Titanium.Web.Proxy
//If request was modified by user
if (request.IsBodyRead)
{
if (request.ContentEncoding != null)
{
request.Body = await GetCompressedResponseBody(request.ContentEncoding, request.Body);
}
bool isChunked = request.IsChunked;
string contentEncoding = request.ContentEncoding;
var body = request.Body;
if (contentEncoding != null && body != null)
{
body = GetCompressedBody(contentEncoding, body);
//chunked send is not supported as of now
request.ContentLength = body.Length;
if (isChunked == false)
{
request.ContentLength = body.Length;
}
else
{
request.ContentLength = -1;
}
}
await args.WebSession.ServerConnection.StreamWriter.WriteAsync(body);
await args.WebSession.ServerConnection.StreamWriter.WriteBodyAsync(body, isChunked);
}
else
{
......@@ -541,7 +559,7 @@ namespace Titanium.Web.Proxy
if (request.HasBody)
{
HttpWriter writer = args.WebSession.ServerConnection.StreamWriter;
await args.CopyRequestBodyAsync(writer, false);
await args.CopyRequestBodyAsync(writer, TransformationMode.None);
}
}
}
......
using System;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Compression;
......@@ -36,7 +38,7 @@ namespace Titanium.Web.Proxy
args.ReRequest = false;
//if user requested call back then do it
if (!response.ResponseLocked)
if (!response.Locked)
{
await InvokeBeforeResponse(args);
}
......@@ -51,7 +53,7 @@ namespace Titanium.Web.Proxy
return;
}
response.ResponseLocked = true;
response.Locked = true;
var clientStreamWriter = args.ProxyClient.ClientStreamWriter;
......@@ -80,22 +82,23 @@ namespace Titanium.Web.Proxy
bool isChunked = response.IsChunked;
string contentEncoding = response.ContentEncoding;
if (contentEncoding != null)
var body = response.Body;
if (contentEncoding != null && body != null)
{
response.Body = await GetCompressedResponseBody(contentEncoding, response.Body);
body = GetCompressedBody(contentEncoding, body);
if (isChunked == false)
{
response.ContentLength = response.Body.Length;
response.ContentLength = body.Length;
}
else
{
response.ContentLength = -1;
}
}
}
await clientStreamWriter.WriteHeadersAsync(response.Headers);
await clientStreamWriter.WriteBodyAsync(response.Body, isChunked);
await clientStreamWriter.WriteBodyAsync(body, isChunked);
}
else
{
......@@ -104,7 +107,7 @@ namespace Titanium.Web.Proxy
//Write body if exists
if (response.HasBody)
{
await args.CopyResponseBodyAsync(clientStreamWriter, false);
await args.CopyResponseBodyAsync(clientStreamWriter, TransformationMode.None);
}
}
}
......@@ -115,15 +118,23 @@ namespace Titanium.Web.Proxy
}
/// <summary>
/// get the compressed response body from give response bytes
/// get the compressed body from given bytes
/// </summary>
/// <param name="encodingType"></param>
/// <param name="responseBodyStream"></param>
/// <param name="body"></param>
/// <returns></returns>
private async Task<byte[]> GetCompressedResponseBody(string encodingType, byte[] responseBodyStream)
private byte[] GetCompressedBody(string encodingType, byte[] body)
{
var compressor = CompressionFactory.GetCompression(encodingType);
return await compressor.Compress(responseBodyStream);
using (var ms = new MemoryStream())
{
using (var zip = compressor.GetStream(ms))
{
zip.Write(body, 0, body.Length);
}
return ms.ToArray();
}
}
......@@ -134,5 +145,13 @@ namespace Titanium.Web.Proxy
await BeforeResponse.InvokeAsync(this, args, ExceptionFunc);
}
}
private async Task InvokeAfterResponse(SessionEventArgs args)
{
if (AfterResponse != null)
{
await AfterResponse.InvokeAsync(this, args, ExceptionFunc);
}
}
}
}
......@@ -13,7 +13,7 @@
<ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.8.1.4" />
<PackageReference Include="StreamExtended" Version="1.0.138-beta" />
<PackageReference Include="StreamExtended" Version="1.0.141-beta" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
......
......@@ -14,7 +14,7 @@
<copyright>Copyright &#x00A9; Titanium. All rights reserved.</copyright>
<tags></tags>
<dependencies>
<dependency id="StreamExtended" version="1.0.138-beta" />
<dependency id="StreamExtended" version="1.0.141-beta" />
<dependency id="Portable.BouncyCastle" version="1.8.1.4" />
</dependencies>
</metadata>
......
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Portable.BouncyCastle" version="1.8.1.4" targetFramework="net45" />
<package id="StreamExtended" version="1.0.138-beta" targetFramework="net45" />
<package id="StreamExtended" version="1.0.141-beta" targetFramework="net45" />
</packages>
\ 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