Commit 5829ff32 authored by Honfika's avatar Honfika

mulipart event now works when user reads the body

parent e946e11c
...@@ -152,8 +152,6 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -152,8 +152,6 @@ namespace Titanium.Web.Proxy.Examples.Basic
requestHeaderHistory[e.Id] = e.WebSession.Request.Headers; requestHeaderHistory[e.Id] = e.WebSession.Request.Headers;
////This sample shows how to get the multipart form data headers ////This sample shows how to get the multipart form data headers
////Do not read the body (GetRequestBody or GetResponseBodyAsString) in this case
////otherwise you have to parse the headers from the returned body data
//if (e.WebSession.Request.Host == "mail.yahoo.com" && e.WebSession.Request.IsMultipartFormData) //if (e.WebSession.Request.Host == "mail.yahoo.com" && e.WebSession.Request.IsMultipartFormData)
//{ //{
// e.MultipartRequestPartSent += MultipartRequestPartSent; // e.MultipartRequestPartSent += MultipartRequestPartSent;
......
...@@ -3,6 +3,7 @@ using System.Collections.Generic; ...@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using StreamExtended.Helpers;
using StreamExtended.Network; using StreamExtended.Network;
using Titanium.Web.Proxy.Decompression; using Titanium.Web.Proxy.Decompression;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
...@@ -43,7 +44,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -43,7 +44,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
internal ProxyClient ProxyClient { get; } internal ProxyClient ProxyClient { get; }
internal bool HasMulipartEventSubscribers => MultipartRequestPartSent != null; private bool hasMulipartEventSubscribers => MultipartRequestPartSent != null;
/// <summary> /// <summary>
/// Returns a unique Id for this request/response session /// Returns a unique Id for this request/response session
...@@ -137,7 +138,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -137,7 +138,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary> /// <summary>
/// Read request body content as bytes[] for current session /// Read request body content as bytes[] for current session
/// </summary> /// </summary>
private async Task ReadRequestBody() private async Task ReadRequestBodyAsync()
{ {
WebSession.Request.EnsureBodyAvailable(false); WebSession.Request.EnsureBodyAvailable(false);
...@@ -146,7 +147,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -146,7 +147,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 ReadBody(ProxyClient.ClientStreamReader, request.IsChunked, request.ContentLength, request.ContentEncoding); var body = await ReadBodyAsync(ProxyClient.ClientStreamReader, request.IsChunked, request.ContentLength, request.ContentEncoding, true);
request.Body = body; request.Body = body;
//Now set the flag to true //Now set the flag to true
...@@ -162,7 +163,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -162,7 +163,7 @@ namespace Titanium.Web.Proxy.EventArguments
internal async Task ClearResponse() internal async Task ClearResponse()
{ {
//siphon out the body //siphon out the body
await ReadResponseBody(); await ReadResponseBodyAsync();
WebSession.Response = new Response(); WebSession.Response = new Response();
} }
...@@ -184,7 +185,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -184,7 +185,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary> /// <summary>
/// Read response body as byte[] for current response /// Read response body as byte[] for current response
/// </summary> /// </summary>
private async Task ReadResponseBody() private async Task ReadResponseBodyAsync()
{ {
if (!WebSession.Request.RequestLocked) if (!WebSession.Request.RequestLocked)
{ {
...@@ -200,7 +201,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -200,7 +201,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 ReadBody(WebSession.ServerConnection.StreamReader, response.IsChunked, response.ContentLength, response.ContentEncoding); var body = await ReadBodyAsync(WebSession.ServerConnection.StreamReader, response.IsChunked, response.ContentLength, response.ContentEncoding, false);
response.Body = body; response.Body = body;
//Now set the flag to true //Now set the flag to true
...@@ -210,14 +211,136 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -210,14 +211,136 @@ namespace Titanium.Web.Proxy.EventArguments
} }
} }
private async Task<byte[]> ReadBody(CustomBinaryReader streamReader, bool isChunked, long contentLength, string contentEncoding) private async Task<byte[]> ReadBodyAsync(CustomBinaryReader reader, bool isChunked, long contentLength, string contentEncoding, bool isRequest)
{ {
//If chunked then its easy just read the whole body with the content length mentioned in the header
using (var bodyStream = new MemoryStream()) using (var bodyStream = new MemoryStream())
{ {
var writer = new HttpWriter(bodyStream, bufferSize); var writer = new HttpWriter(bodyStream, bufferSize);
await writer.CopyBodyAsync(streamReader, isChunked, contentLength, true); if (isRequest)
return await GetDecompressedBody(contentEncoding, bodyStream.ToArray()); {
await CopyRequestBodyAsync(writer, true);
}
else
{
await CopyResponseBodyAsync(writer, true);
}
return await GetDecompressedBodyAsync(contentEncoding, bodyStream.ToArray());
}
}
/// <summary>
/// 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)
{
// End the operation
var request = WebSession.Request;
var reader = ProxyClient.ClientStreamReader;
long contentLength = request.ContentLength;
//send the request body bytes to server
if (contentLength > 0 && hasMulipartEventSubscribers && request.IsMultipartFormData)
{
string boundary = HttpHelper.GetBoundaryFromContentType(request.ContentType);
using (var copyStream = new CopyStream(reader, writer, bufferSize))
using (var copyStreamReader = new CustomBinaryReader(copyStream, bufferSize))
{
while (contentLength > copyStream.ReadBytes)
{
long read = await ReadUntilBoundaryAsync(copyStreamReader, contentLength, boundary);
if (read == 0)
{
break;
}
if (contentLength > copyStream.ReadBytes)
{
var headers = new HeaderCollection();
await HeaderParser.ReadHeaders(copyStreamReader, headers);
OnMultipartRequestPartSent(boundary, headers);
}
}
await copyStream.FlushAsync();
}
}
else
{
await writer.CopyBodyAsync(reader, request.IsChunked, contentLength, removeChunkedEncoding);
}
}
internal async Task CopyResponseBodyAsync(HttpWriter writer, bool removeChunkedEncoding)
{
var response = WebSession.Response;
var reader = WebSession.ServerConnection.StreamReader;
await writer.CopyBodyAsync(reader, response.IsChunked, response.ContentLength, removeChunkedEncoding);
}
/// <summary>
/// Read a line from the byte stream
/// </summary>
/// <returns></returns>
private async Task<long> ReadUntilBoundaryAsync(CustomBinaryReader reader, long totalBytesToRead, string boundary)
{
int bufferDataLength = 0;
var buffer = BufferPool.GetBuffer(bufferSize);
try
{
int boundaryLength = boundary.Length + 4;
long bytesRead = 0;
while (bytesRead < totalBytesToRead && (reader.DataAvailable || await reader.FillBufferAsync()))
{
byte newChar = reader.ReadByteFromBuffer();
buffer[bufferDataLength] = newChar;
bufferDataLength++;
bytesRead++;
if (bufferDataLength >= boundaryLength)
{
int startIdx = bufferDataLength - boundaryLength;
if (buffer[startIdx] == '-' && buffer[startIdx + 1] == '-')
{
startIdx += 2;
bool ok = true;
for (int i = 0; i < boundary.Length; i++)
{
if (buffer[startIdx + i] != boundary[i])
{
ok = false;
break;
}
}
if (ok)
{
break;
}
}
}
if (bufferDataLength == buffer.Length)
{
//boundary is not longer than 70 bytes according to the specification, so keeping the last 100 (minimum 74) bytes is enough
const int bytesToKeep = 100;
Buffer.BlockCopy(buffer, buffer.Length - bytesToKeep, buffer, 0, bytesToKeep);
bufferDataLength = bytesToKeep;
}
}
return bytesRead;
}
finally
{
BufferPool.ReturnBuffer(buffer);
} }
} }
...@@ -229,7 +352,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -229,7 +352,7 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
if (!WebSession.Request.IsBodyRead) if (!WebSession.Request.IsBodyRead)
{ {
await ReadRequestBody(); await ReadRequestBodyAsync();
} }
return WebSession.Request.Body; return WebSession.Request.Body;
...@@ -243,7 +366,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -243,7 +366,7 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
if (!WebSession.Request.IsBodyRead) if (!WebSession.Request.IsBodyRead)
{ {
await ReadRequestBody(); await ReadRequestBodyAsync();
} }
return WebSession.Request.BodyString; return WebSession.Request.BodyString;
...@@ -263,7 +386,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -263,7 +386,7 @@ namespace Titanium.Web.Proxy.EventArguments
//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 (!WebSession.Request.IsBodyRead)
{ {
await ReadRequestBody(); await ReadRequestBodyAsync();
} }
WebSession.Request.Body = body; WebSession.Request.Body = body;
...@@ -284,7 +407,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -284,7 +407,7 @@ namespace Titanium.Web.Proxy.EventArguments
//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 (!WebSession.Request.IsBodyRead)
{ {
await ReadRequestBody(); await ReadRequestBodyAsync();
} }
await SetRequestBody(WebSession.Request.Encoding.GetBytes(body)); await SetRequestBody(WebSession.Request.Encoding.GetBytes(body));
...@@ -298,7 +421,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -298,7 +421,7 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
if (!WebSession.Response.IsBodyRead) if (!WebSession.Response.IsBodyRead)
{ {
await ReadResponseBody(); await ReadResponseBodyAsync();
} }
return WebSession.Response.Body; return WebSession.Response.Body;
...@@ -312,7 +435,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -312,7 +435,7 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
if (!WebSession.Response.IsBodyRead) if (!WebSession.Response.IsBodyRead)
{ {
await ReadResponseBody(); await ReadResponseBodyAsync();
} }
return WebSession.Response.BodyString; return WebSession.Response.BodyString;
...@@ -370,7 +493,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -370,7 +493,7 @@ namespace Titanium.Web.Proxy.EventArguments
await SetResponseBody(bodyBytes); await SetResponseBody(bodyBytes);
} }
private async Task<byte[]> GetDecompressedBody(string encodingType, byte[] bodyStream) private async Task<byte[]> GetDecompressedBodyAsync(string encodingType, byte[] bodyStream)
{ {
var decompressionFactory = new DecompressionFactory(); var decompressionFactory = new DecompressionFactory();
var decompressor = decompressionFactory.Create(encodingType); var decompressor = decompressionFactory.Create(encodingType);
......
...@@ -510,7 +510,8 @@ namespace Titanium.Web.Proxy ...@@ -510,7 +510,8 @@ namespace Titanium.Web.Proxy
//If its a post/put/patch request, then read the client html body and send it to server //If its a post/put/patch request, then read the client html body and send it to server
if (request.HasBody) if (request.HasBody)
{ {
await SendClientRequestBody(args); HttpWriter writer = args.WebSession.ServerConnection.StreamWriter;
await args.CopyRequestBodyAsync(writer, false);
} }
} }
} }
...@@ -604,111 +605,5 @@ namespace Titanium.Web.Proxy ...@@ -604,111 +605,5 @@ namespace Titanium.Web.Proxy
requestHeaders.FixProxyHeaders(); requestHeaders.FixProxyHeaders();
} }
/// <summary>
/// This is called when the request is PUT/POST/PATCH to read the body
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private async Task SendClientRequestBody(SessionEventArgs args)
{
// End the operation
var request = args.WebSession.Request;
var reader = args.ProxyClient.ClientStreamReader;
var writer = args.WebSession.ServerConnection.StreamWriter;
//send the request body bytes to server
if (request.ContentLength > 0 && args.HasMulipartEventSubscribers && request.IsMultipartFormData)
{
string boundary = HttpHelper.GetBoundaryFromContentType(request.ContentType);
long bytesToSend = request.ContentLength;
var copyStream = new CopyStream(reader, writer, BufferSize);
var copyStreamReader = new CustomBinaryReader(copyStream, BufferSize);
while (bytesToSend > copyStream.ReadBytes)
{
long read = await ReadUntilBoundaryAsync(copyStreamReader, bytesToSend, boundary);
if (read == 0)
{
break;
}
if (bytesToSend > copyStream.ReadBytes)
{
var headers = new HeaderCollection();
await HeaderParser.ReadHeaders(copyStreamReader, headers);
args.OnMultipartRequestPartSent(boundary, headers);
}
}
await copyStream.FlushAsync();
}
else
{
await writer.CopyBodyAsync(args.ProxyClient.ClientStreamReader, request.IsChunked, request.ContentLength, false);
}
}
/// <summary>
/// Read a line from the byte stream
/// </summary>
/// <returns></returns>
private async Task<long> ReadUntilBoundaryAsync(CustomBinaryReader reader, long totalBytesToRead, string boundary)
{
int bufferDataLength = 0;
var buffer = BufferPool.GetBuffer(BufferSize);
try
{
int boundaryLength = boundary.Length + 4;
long bytesRead = 0;
while (bytesRead < totalBytesToRead && (reader.DataAvailable || await reader.FillBufferAsync()))
{
byte newChar = reader.ReadByteFromBuffer();
buffer[bufferDataLength] = newChar;
bufferDataLength++;
bytesRead++;
if (bufferDataLength >= boundaryLength)
{
int startIdx = bufferDataLength - boundaryLength;
if (buffer[startIdx] == '-' && buffer[startIdx + 1] == '-')
{
startIdx += 2;
bool ok = true;
for (int i = 0; i < boundary.Length; i++)
{
if (buffer[startIdx + i] != boundary[i])
{
ok = false;
break;
}
}
if (ok)
{
break;
}
}
}
if (bufferDataLength == buffer.Length)
{
//boundary is not longer than 70 bytes according to the specification, so keeping the last 100 (minimum 74) bytes is enough
const int bytesToKeep = 100;
Buffer.BlockCopy(buffer, buffer.Length - bytesToKeep, buffer, 0, bytesToKeep);
bufferDataLength = bytesToKeep;
}
}
return bytesRead;
}
finally
{
BufferPool.ReturnBuffer(buffer);
}
}
} }
} }
...@@ -105,8 +105,7 @@ namespace Titanium.Web.Proxy ...@@ -105,8 +105,7 @@ namespace Titanium.Web.Proxy
//Write body if exists //Write body if exists
if (response.HasBody) if (response.HasBody)
{ {
await clientStreamWriter.CopyBodyAsync(args.WebSession.ServerConnection.StreamReader, await args.CopyResponseBodyAsync(clientStreamWriter, false);
response.IsChunked, response.ContentLength, false);
} }
} }
} }
......
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