Commit 973c6219 authored by Honfika's avatar Honfika

remove chunk encoding and compress as a stream (inside readbodyasync), so...

remove chunk encoding and compress as a stream (inside readbodyasync), so later it can be made public
parent 6e209022
......@@ -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)
......
......@@ -8,8 +8,8 @@ namespace Titanium.Web.Proxy.Compression
internal 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)
{
......
......@@ -5,6 +5,8 @@
/// </summary>
internal class DecompressionFactory
{
public static DecompressionFactory Instance = new DecompressionFactory();
internal IDecompression Create(string type)
{
switch (type)
......
using System.Threading.Tasks;
using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Decompression
{
/// <summary>
/// When no compression is specified just return the byte array
/// When no compression is specified just return the stream
/// </summary>
internal class DefaultDecompression : IDecompression
{
public Task<byte[]> Decompress(byte[] compressedArray, int bufferSize)
public Stream GetStream(Stream stream)
{
return Task.FromResult(compressedArray);
return stream;
}
}
}
......@@ -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);
}
}
}
......@@ -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);
}
}
}
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 ChunkedStream : Stream
{
private readonly CustomBufferedStream baseStream;
private readonly CustomBinaryReader baseReader;
private bool readChunkTrail;
private int chunkBytesRemaining;
public ChunkedStream(CustomBufferedStream baseStream, CustomBinaryReader baseReader)
{
this.baseStream = baseStream;
this.baseReader = baseReader;
}
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);
chunkBytesRemaining = chunkSize;
if (chunkSize == 0)
{
chunkBytesRemaining = -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 (chunkBytesRemaining == -1)
{
return 0;
}
if (chunkBytesRemaining == 0)
{
GetNextChunk();
}
if (chunkBytesRemaining == -1)
{
return 0;
}
int toRead = Math.Min(count, chunkBytesRemaining);
int res = baseStream.Read(buffer, offset, toRead);
chunkBytesRemaining -= res;
return res;
}
public async Task Finish()
{
var buffer = BufferPool.GetBuffer(baseReader.Buffer.Length);
try
{
while (chunkBytesRemaining != -1)
{
await ReadAsync(buffer, 0, buffer.Length);
}
}
finally
{
BufferPool.ReturnBuffer(buffer);
}
}
protected override void Dispose(bool disposing)
{
if (chunkBytesRemaining != -1)
{
;
}
base.Dispose(disposing);
}
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.Threading.Tasks;
......@@ -100,6 +101,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 +149,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
......@@ -221,7 +224,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
......@@ -231,21 +234,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();
}
}
......@@ -253,7 +256,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;
......@@ -290,16 +293,67 @@ namespace Titanium.Web.Proxy.EventArguments
}
else
{
await writer.CopyBodyAsync(reader, request.IsChunked, contentLength, removeChunkedEncoding, OnDataSent);
await CopyBodyAsync(ProxyClient.ClientStream, reader, writer,
request.IsChunked, transformation, request.ContentEncoding, contentLength, OnDataSent);
}
}
private async Task CopyBodyAsync(CustomBufferedStream stream, CustomBinaryReader reader, HttpWriter writer,
bool isChunked, TransformationMode transformation, string contentEncoding, long contentLength,
Action<byte[], int, int> onCopy)
{
bool newReader = false;
Stream s = stream;
ChunkedStream chunkedStream = null;
Stream decompressStream = null;
if (isChunked && transformation != TransformationMode.None)
{
s = chunkedStream = new ChunkedStream(stream, reader);
isChunked = false;
newReader = true;
}
internal async Task CopyResponseBodyAsync(HttpWriter writer, bool removeChunkedEncoding)
if (transformation == TransformationMode.Uncompress)
{
s = decompressStream = DecompressionFactory.Instance.Create(contentEncoding).GetStream(s);
newReader = true;
}
try
{
if (newReader)
{
var bufStream = new CustomBufferedStream(s, bufferSize);
s = bufStream;
reader = new CustomBinaryReader(bufStream, bufferSize);
}
await writer.CopyBodyAsync(reader, isChunked, contentLength, onCopy);
}
finally
{
if (newReader)
{
reader?.Dispose();
decompressStream?.Dispose();
if (chunkedStream != null)
{
await chunkedStream.Finish();
chunkedStream.Dispose();
}
}
}
}
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, OnDataReceived);
await CopyBodyAsync(WebSession.ServerConnection.Stream, reader, writer,
response.IsChunked, transformation, response.ContentEncoding, response.ContentLength, OnDataReceived);
}
/// <summary>
......@@ -513,14 +567,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
......@@ -642,6 +688,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,17 +153,16 @@ 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, Action<byte[], int, int> onCopy)
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, onCopy);
return CopyBodyChunkedAsync(streamReader, onCopy);
}
//http 1.0
......@@ -197,30 +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, Action<byte[], int, int> onCopy)
private async Task CopyBodyChunkedAsync(CustomBinaryReader reader, Action<byte[], int, int> onCopy)
{
while (true)
{
string chunkHead = await reader.ReadLineAsync();
int idx = chunkHead.IndexOf(";");
if (idx >= 0)
{
// remove chunk extension
chunkHead = chunkHead.Substring(0, idx);
}
int chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);
if (!removeChunkedEncoding)
{
await WriteLineAsync(chunkHead);
}
if (chunkSize != 0)
{
await CopyBytesFromStream(reader, chunkSize, onCopy);
}
if (!removeChunkedEncoding)
{
await WriteLineAsync();
}
//chunk trail
await reader.ReadLineAsync();
......
......@@ -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>
......
......@@ -306,6 +306,8 @@ namespace Titanium.Web.Proxy
WebSession = { ConnectRequest = connectRequest }
};
try
{
try
{
Request.ParseRequestLine(httpCmd, out string httpMethod, out string httpUrl, out var version);
......@@ -419,7 +421,8 @@ namespace Titanium.Web.Proxy
await connection.StreamWriter.WriteHeadersAsync(requestHeaders);
string httpStatus = await connection.StreamReader.ReadLineAsync();
Response.ParseResponseLine(httpStatus, out var responseVersion, out int responseStatusCode, out string responseStatusDescription);
Response.ParseResponseLine(httpStatus, out var responseVersion, out int responseStatusCode,
out string responseStatusDescription);
response.HttpVersion = responseVersion;
response.StatusCode = responseStatusCode;
response.StatusDescription = responseStatusDescription;
......@@ -458,8 +461,14 @@ namespace Titanium.Web.Proxy
{
throw new ProxyHttpException("Error occured whilst handling session request", e, args);
}
}
catch (Exception e)
{
args.Exception = e;
}
finally
{
await InvokeAfterResponse(args);
args.Dispose();
}
}
......@@ -541,7 +550,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);
}
}
}
......
......@@ -80,13 +80,14 @@ namespace Titanium.Web.Proxy
bool isChunked = response.IsChunked;
string contentEncoding = response.ContentEncoding;
var body = response.Body;
if (contentEncoding != null)
{
response.Body = await GetCompressedResponseBody(contentEncoding, response.Body);
body = await GetCompressedResponseBody(contentEncoding, body);
if (isChunked == false)
{
response.ContentLength = response.Body.Length;
response.ContentLength = body.Length;
}
else
{
......@@ -95,7 +96,7 @@ namespace Titanium.Web.Proxy
}
await clientStreamWriter.WriteHeadersAsync(response.Headers);
await clientStreamWriter.WriteBodyAsync(response.Body, isChunked);
await clientStreamWriter.WriteBodyAsync(body, isChunked);
}
else
{
......@@ -104,7 +105,7 @@ namespace Titanium.Web.Proxy
//Write body if exists
if (response.HasBody)
{
await args.CopyResponseBodyAsync(clientStreamWriter, false);
await args.CopyResponseBodyAsync(clientStreamWriter, TransformationMode.None);
}
}
}
......@@ -134,5 +135,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);
}
}
}
}
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