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