Commit 2d880884 authored by titanium007's avatar titanium007

Fix POST chunked file upload

parent c084ccb6
...@@ -59,7 +59,7 @@ Sample request and response event handlers ...@@ -59,7 +59,7 @@ Sample request and response event handlers
Console.WriteLine(e.RequestURL); Console.WriteLine(e.RequestURL);
//modify request headers //read request headers
var requestHeaders = e.RequestHeaders; var requestHeaders = e.RequestHeaders;
if ((e.RequestMethod.ToUpper() == "POST" || e.RequestMethod.ToUpper() == "PUT") && e.RequestContentLength > 0) if ((e.RequestMethod.ToUpper() == "POST" || e.RequestMethod.ToUpper() == "PUT") && e.RequestContentLength > 0)
...@@ -87,9 +87,10 @@ Sample request and response event handlers ...@@ -87,9 +87,10 @@ Sample request and response event handlers
public void OnResponse(object sender, SessionEventArgs e) public void OnResponse(object sender, SessionEventArgs e)
{ {
//modify response headers //read response headers
var responseHeaders = e.ResponseHeaders; var responseHeaders = e.ResponseHeaders;
if (e.ResponseStatusCode == HttpStatusCode.OK) if (e.ResponseStatusCode == HttpStatusCode.OK)
{ {
if (e.ResponseContentType.Trim().ToLower().Contains("text/html")) if (e.ResponseContentType.Trim().ToLower().Contains("text/html"))
...@@ -101,7 +102,7 @@ Sample request and response event handlers ...@@ -101,7 +102,7 @@ Sample request and response event handlers
//Get response body as string //Get response body as string
string responseBody = e.GetResponseBodyAsString(); string responseBody = e.GetResponseBodyAsString();
//Inject script in to body //Modify e.ServerResponse
Regex rex = new Regex("</body>", RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.Multiline); Regex rex = new Regex("</body>", RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.Multiline);
string modified = rex.Replace(responseBody, "<script type =\"text/javascript\">alert('Response was modified by this script!');</script></body>", 1); string modified = rex.Replace(responseBody, "<script type =\"text/javascript\">alert('Response was modified by this script!');</script></body>", 1);
......
...@@ -60,10 +60,10 @@ namespace Titanium.Web.Proxy.Test ...@@ -60,10 +60,10 @@ namespace Titanium.Web.Proxy.Test
Console.WriteLine(e.RequestURL); Console.WriteLine(e.RequestURL);
////modify request headers ////read request headers
//var requestHeaders = e.RequestHeaders; //var requestHeaders = e.RequestHeaders;
//if ((e.RequestMethod.ToUpper() == "POST" || e.RequestMethod.ToUpper() == "PUT") && e.RequestContentLength > 0) //if ((e.RequestMethod.ToUpper() == "POST" || e.RequestMethod.ToUpper() == "PUT"))
//{ //{
// //Get/Set request body bytes // //Get/Set request body bytes
// byte[] bodyBytes = e.GetRequestBody(); // byte[] bodyBytes = e.GetRequestBody();
...@@ -89,7 +89,7 @@ namespace Titanium.Web.Proxy.Test ...@@ -89,7 +89,7 @@ namespace Titanium.Web.Proxy.Test
//Insert script to read the Browser URL and send it back to proxy //Insert script to read the Browser URL and send it back to proxy
public void OnResponse(object sender, SessionEventArgs e) public void OnResponse(object sender, SessionEventArgs e)
{ {
////modify response headers ////read response headers
//var responseHeaders = e.ResponseHeaders; //var responseHeaders = e.ResponseHeaders;
......
...@@ -21,6 +21,14 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -21,6 +21,14 @@ namespace Titanium.Web.Proxy.EventArguments
internal CustomBinaryReader clientStreamReader { get; set; } internal CustomBinaryReader clientStreamReader { get; set; }
internal StreamWriter clientStreamWriter { get; set; } internal StreamWriter clientStreamWriter { get; set; }
internal bool isHttps { get; set; }
internal string requestURL { get; set; }
internal string requestHostname { get; set; }
internal int clientPort { get; set; }
internal IPAddress clientIpAddress { get; set; }
internal Encoding requestEncoding { get; set; } internal Encoding requestEncoding { get; set; }
internal Version requestHttpVersion { get; set; } internal Version requestHttpVersion { get; set; }
internal bool requestIsAlive { get; set; } internal bool requestIsAlive { get; set; }
...@@ -28,25 +36,19 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -28,25 +36,19 @@ namespace Titanium.Web.Proxy.EventArguments
internal byte[] requestBody { get; set; } internal byte[] requestBody { get; set; }
internal string requestBodyString { get; set; } internal string requestBodyString { get; set; }
internal bool requestBodyRead { get; set; } internal bool requestBodyRead { get; set; }
internal List<HttpHeader> requestHeaders { get; set; }
internal bool RequestLocked { get; set; }
internal HttpWebRequest proxyRequest { get; set; }
internal Encoding responseEncoding { get; set; } internal Encoding responseEncoding { get; set; }
internal Stream responseStream { get; set; } internal Stream responseStream { get; set; }
internal byte[] responseBody { get; set; } internal byte[] responseBody { get; set; }
internal string responseBodyString { get; set; } internal string responseBodyString { get; set; }
internal bool responseBodyRead { get; set; } internal bool responseBodyRead { get; set; }
internal List<HttpHeader> responseHeaders { get; set; }
internal bool RequestLocked { get; set; }
internal HttpWebRequest proxyRequest { get; set; }
internal bool ResponseLocked { get; set; } internal bool ResponseLocked { get; set; }
internal HttpWebResponse serverResponse { get; set; } internal HttpWebResponse serverResponse { get; set; }
internal bool isHttps { get; set; }
internal string requestURL { get; set; }
internal string requestHostname { get; set; }
internal int clientPort { get; set; }
internal IPAddress clientIpAddress { get; set; }
public int ClientPort { get { return this.clientPort; } } public int ClientPort { get { return this.clientPort; } }
public IPAddress ClientIpAddress { get { return this.clientIpAddress; } } public IPAddress ClientIpAddress { get { return this.clientIpAddress; } }
...@@ -56,14 +58,17 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -56,14 +58,17 @@ namespace Titanium.Web.Proxy.EventArguments
public string RequestURL { get { return this.requestURL; } } public string RequestURL { get { return this.requestURL; } }
public string RequestHostname { get { return this.requestHostname; } } public string RequestHostname { get { return this.requestHostname; } }
public List<HttpHeader> RequestHeaders { get { return this.requestHeaders; } }
public List<HttpHeader> ResponseHeaders { get { return this.responseHeaders; } }
public int RequestContentLength public int RequestContentLength
{ {
get get
{ {
if (this.RequestHeaders.Any(x => x.Name.ToLower() == "content-length")) if (this.requestHeaders.Any(x => x.Name.ToLower() == "content-length"))
{ {
int contentLen; int contentLen;
int.TryParse(this.RequestHeaders.First(x => x.Name.ToLower() == "content-length").Value, out contentLen); int.TryParse(this.requestHeaders.First(x => x.Name.ToLower() == "content-length").Value, out contentLen);
if (contentLen != 0) if (contentLen != 0)
return contentLen; return contentLen;
} }
...@@ -72,11 +77,11 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -72,11 +77,11 @@ namespace Titanium.Web.Proxy.EventArguments
} }
public string RequestMethod { get { return this.proxyRequest.Method; } } public string RequestMethod { get { return this.proxyRequest.Method; } }
public List<HttpHeader> RequestHeaders { get; set; }
public HttpStatusCode ResponseStatusCode { get { return this.serverResponse.StatusCode; } } public HttpStatusCode ResponseStatusCode { get { return this.serverResponse.StatusCode; } }
public string ResponseContentType { get { return this.ResponseHeaders.Any(x => x.Name.ToLower() == "content-type") ? this.ResponseHeaders.First(x => x.Name.ToLower() == "content-type").Value : null; } } public string ResponseContentType { get { return this.responseHeaders.Any(x => x.Name.ToLower() == "content-type") ? this.responseHeaders.First(x => x.Name.ToLower() == "content-type").Value : null; } }
public List<HttpHeader> ResponseHeaders { get; set; }
internal SessionEventArgs(int bufferSize) internal SessionEventArgs(int bufferSize)
{ {
...@@ -85,9 +90,88 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -85,9 +90,88 @@ namespace Titanium.Web.Proxy.EventArguments
private void readRequestBody() private void readRequestBody()
{ {
if ((proxyRequest.Method.ToUpper() != "POST" && proxyRequest.Method.ToUpper() != "PUT"))
{
throw new BodyNotFoundException("Request don't have a body." +
"Please verify that this request is a Http POST/PUT and request content length is greater than zero before accessing the body.");
}
if (requestBody == null) if (requestBody == null)
{ {
bool isChunked = false;
string requestContentEncoding = null;
if (requestHeaders.Any(x => x.Name.ToLower() == "content-encoding"))
{
requestContentEncoding = requestHeaders.First(x => x.Name.ToLower() == "content-encoding").Value;
}
if (requestHeaders.Any(x => x.Name.ToLower() == "transfer-encoding"))
{
var transferEncoding = requestHeaders.First(x => x.Name.ToLower() == "transfer-encoding").Value.ToLower();
if (transferEncoding.Contains("chunked"))
{
isChunked = true;
}
}
if (requestContentEncoding == null && !isChunked)
requestBody = clientStreamReader.ReadBytes(RequestContentLength); requestBody = clientStreamReader.ReadBytes(RequestContentLength);
else
{
using (var requestBodyStream = new MemoryStream())
{
if (isChunked)
{
while (true)
{
var chuchkHead = clientStreamReader.ReadLine();
var chunkSize = int.Parse(chuchkHead, System.Globalization.NumberStyles.HexNumber);
if (chunkSize != 0)
{
var buffer = clientStreamReader.ReadBytes(chunkSize);
requestBodyStream.Write(buffer, 0, buffer.Length);
var chunkTrail = clientStreamReader.ReadLine();
}
else
{
clientStreamReader.ReadLine();
break;
}
}
}
try
{
switch (requestContentEncoding)
{
case "gzip":
requestBody = CompressionHelper.DecompressGzip(requestBodyStream);
break;
case "deflate":
requestBody = CompressionHelper.DecompressDeflate(requestBodyStream);
break;
case "zlib":
requestBody = CompressionHelper.DecompressGzip(requestBodyStream);
break;
default:
requestBody = requestBodyStream.ToArray();
break;
}
}
catch {
requestBody = requestBodyStream.ToArray();
}
}
}
} }
requestBodyRead = true; requestBodyRead = true;
} }
...@@ -95,6 +179,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -95,6 +179,7 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
if (responseBody == null) if (responseBody == null)
{ {
switch (serverResponse.ContentEncoding) switch (serverResponse.ContentEncoding)
{ {
case "gzip": case "gzip":
...@@ -144,23 +229,16 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -144,23 +229,16 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
if (RequestLocked) throw new Exception("You cannot call this function after request is made to server."); if (RequestLocked) throw new Exception("You cannot call this function after request is made to server.");
if ((proxyRequest.Method.ToUpper() == "POST" || proxyRequest.Method.ToUpper() == "PUT") && RequestContentLength > 0)
{
readRequestBody(); readRequestBody();
return requestBody; return requestBody;
}
else
throw new BodyNotFoundException("Request don't have a body." +
"Please verify that this request is a Http POST/PUT and request content length is greater than zero before accessing the body.");
} }
public string GetRequestBodyAsString() public string GetRequestBodyAsString()
{ {
if (RequestLocked) throw new Exception("You cannot call this function after request is made to server."); if (RequestLocked) throw new Exception("You cannot call this function after request is made to server.");
if ((proxyRequest.Method.ToUpper() == "POST" || proxyRequest.Method.ToUpper() == "PUT") && RequestContentLength > 0)
{
readRequestBody(); readRequestBody();
if (requestBodyString == null) if (requestBodyString == null)
...@@ -168,10 +246,8 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -168,10 +246,8 @@ namespace Titanium.Web.Proxy.EventArguments
requestBodyString = requestEncoding.GetString(requestBody); requestBodyString = requestEncoding.GetString(requestBody);
} }
return requestBodyString; return requestBodyString;
}
else
throw new BodyNotFoundException("Request don't have a body." +
"Please verify that this request is a Http POST/PUT and request content length is greater than zero before accessing the body.");
} }
...@@ -302,7 +378,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -302,7 +378,6 @@ namespace Titanium.Web.Proxy.EventArguments
} }
} }
} }
\ No newline at end of file
...@@ -59,7 +59,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -59,7 +59,7 @@ namespace Titanium.Web.Proxy.Helpers
public static byte[] DecompressGzip(Stream input) public static byte[] DecompressGzip(Stream input)
{ {
using (System.IO.Compression.GZipStream decompressor = new System.IO.Compression.GZipStream(input, System.IO.Compression.CompressionMode.Decompress)) using (var decompressor = new System.IO.Compression.GZipStream(input, System.IO.Compression.CompressionMode.Decompress))
{ {
int read = 0; int read = 0;
......
...@@ -150,26 +150,26 @@ namespace Titanium.Web.Proxy ...@@ -150,26 +150,26 @@ namespace Titanium.Web.Proxy
args.isHttps = true; args.isHttps = true;
} }
args.RequestHeaders = new List<HttpHeader>(); args.requestHeaders = new List<HttpHeader>();
string tmpLine = null; string tmpLine = null;
while (!String.IsNullOrEmpty(tmpLine = clientStreamReader.ReadLine())) while (!String.IsNullOrEmpty(tmpLine = clientStreamReader.ReadLine()))
{ {
String[] header = tmpLine.Split(colonSpaceSplit, 2, StringSplitOptions.None); String[] header = tmpLine.Split(colonSpaceSplit, 2, StringSplitOptions.None);
args.RequestHeaders.Add(new HttpHeader(header[0], header[1])); args.requestHeaders.Add(new HttpHeader(header[0], header[1]));
} }
for (int i = 0; i < args.RequestHeaders.Count; i++) for (int i = 0; i < args.requestHeaders.Count; i++)
{ {
var rawHeader = args.RequestHeaders[i]; var rawHeader = args.requestHeaders[i];
//if request was upgrade to web-socket protocol then relay the request without proxying //if request was upgrade to web-socket protocol then relay the request without proxying
if ((rawHeader.Name.ToLower() == "upgrade") && (rawHeader.Value.ToLower() == "websocket")) if ((rawHeader.Name.ToLower() == "upgrade") && (rawHeader.Value.ToLower() == "websocket"))
{ {
TcpHelper.SendRaw(clientStreamReader.BaseStream, httpCmd, args.RequestHeaders, httpRemoteUri.Host, httpRemoteUri.Port, httpRemoteUri.Scheme == Uri.UriSchemeHttps); TcpHelper.SendRaw(clientStreamReader.BaseStream, httpCmd, args.requestHeaders, httpRemoteUri.Host, httpRemoteUri.Port, httpRemoteUri.Scheme == Uri.UriSchemeHttps);
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args); Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
return; return;
} }
...@@ -212,7 +212,7 @@ namespace Titanium.Web.Proxy ...@@ -212,7 +212,7 @@ namespace Titanium.Web.Proxy
return; return;
} }
SetRequestHeaders(args.RequestHeaders, args.proxyRequest); SetRequestHeaders(args.requestHeaders, args.proxyRequest);
//If request was modified by user //If request was modified by user
if (args.requestBodyRead) if (args.requestBodyRead)
...@@ -230,7 +230,6 @@ namespace Titanium.Web.Proxy ...@@ -230,7 +230,6 @@ namespace Titanium.Web.Proxy
if (httpMethod.ToUpper() == "POST" || httpMethod.ToUpper() == "PUT") if (httpMethod.ToUpper() == "POST" || httpMethod.ToUpper() == "PUT")
{ {
SendClientRequestBody(args); SendClientRequestBody(args);
} }
//Http request body sent, now wait asynchronously for response //Http request body sent, now wait asynchronously for response
args.proxyRequest.BeginGetResponse(new AsyncCallback(HandleHttpSessionResponse), args); args.proxyRequest.BeginGetResponse(new AsyncCallback(HandleHttpSessionResponse), args);
...@@ -280,6 +279,7 @@ namespace Titanium.Web.Proxy ...@@ -280,6 +279,7 @@ namespace Titanium.Web.Proxy
case "connection": case "connection":
if (requestHeaders[i].Value.ToLower() == "keep-alive") if (requestHeaders[i].Value.ToLower() == "keep-alive")
webRequest.KeepAlive = true; webRequest.KeepAlive = true;
break; break;
case "content-length": case "content-length":
int contentLen; int contentLen;
...@@ -325,7 +325,7 @@ namespace Titanium.Web.Proxy ...@@ -325,7 +325,7 @@ namespace Titanium.Web.Proxy
//revisit this, transfer-encoding is not a request header according to spec //revisit this, transfer-encoding is not a request header according to spec
//But how to identify if client is sending chunked body for PUT/POST? //But how to identify if client is sending chunked body for PUT/POST?
case "transfer-encoding": case "transfer-encoding":
if (requestHeaders[i].Value.ToLower() == "chunked") if (requestHeaders[i].Value.ToLower().Contains("chunked"))
webRequest.SendChunked = true; webRequest.SendChunked = true;
else else
webRequest.SendChunked = false; webRequest.SendChunked = false;
...@@ -401,62 +401,31 @@ namespace Titanium.Web.Proxy ...@@ -401,62 +401,31 @@ namespace Titanium.Web.Proxy
else if (args.proxyRequest.SendChunked) else if (args.proxyRequest.SendChunked)
{ {
args.proxyRequest.AllowWriteStreamBuffering = true; args.proxyRequest.AllowWriteStreamBuffering = true;
try try
{ {
StringBuilder sb = new StringBuilder();
byte[] byteRead = new byte[1];
while (true) while (true)
{ {
var chuchkHead = args.clientStreamReader.ReadLine();
var chunkSize = int.Parse(chuchkHead, System.Globalization.NumberStyles.HexNumber);
args.clientStream.Read(byteRead, 0, 1); if (chunkSize != 0)
sb.Append(Encoding.ASCII.GetString(byteRead));
if (sb.ToString().EndsWith(Environment.NewLine))
{
var chunkSizeInHex = sb.ToString().Replace(Environment.NewLine, String.Empty);
var chunckSize = int.Parse(chunkSizeInHex, System.Globalization.NumberStyles.HexNumber);
if (chunckSize == 0)
{ {
for (int i = 0; i < Encoding.ASCII.GetByteCount(Environment.NewLine); i++) var buffer = args.clientStreamReader.ReadBytes(chunkSize);
{ postStream.Write(buffer, 0, buffer.Length);
args.clientStream.ReadByte();
} var chunkTrail = args.clientStreamReader.ReadLine();
break;
}
var totalbytesRead = 0;
int bytesToRead;
if (chunckSize < BUFFER_SIZE)
{
bytesToRead = chunckSize;
} }
else else
bytesToRead = BUFFER_SIZE;
while (totalbytesRead < chunckSize)
{
var buffer = args.clientStreamReader.ReadBytes(bytesToRead);
totalbytesRead += buffer.Length;
int RemainingBytes = chunckSize - totalbytesRead;
if (RemainingBytes < bytesToRead)
{ {
bytesToRead = RemainingBytes; args.clientStreamReader.ReadLine();
}
postStream.Write(buffer, 0, buffer.Length);
break;
} }
for (int i = 0; i < Encoding.ASCII.GetByteCount(Environment.NewLine); i++)
{
args.clientStream.ReadByte();
} }
sb.Clear();
}
}
postStream.Close(); postStream.Close();
} }
catch catch
......
...@@ -38,7 +38,7 @@ namespace Titanium.Web.Proxy ...@@ -38,7 +38,7 @@ namespace Titanium.Web.Proxy
{ {
if (args.serverResponse != null) if (args.serverResponse != null)
{ {
args.ResponseHeaders = ReadResponseHeaders(args.serverResponse); args.responseHeaders = ReadResponseHeaders(args.serverResponse);
args.responseStream = args.serverResponse.GetResponseStream(); args.responseStream = args.serverResponse.GetResponseStream();
...@@ -52,11 +52,11 @@ namespace Titanium.Web.Proxy ...@@ -52,11 +52,11 @@ namespace Titanium.Web.Proxy
if (args.responseBodyRead) if (args.responseBodyRead)
{ {
bool isChunked = args.ResponseHeaders.Any(x => x.Name.ToLower() == "transfer-encoding") == false ? false : args.ResponseHeaders.First(x => x.Name.ToLower() == "transfer-encoding").Value.ToLower() == "chunked" ? true : false; bool isChunked = args.serverResponse.GetResponseHeader("transfer-encoding") == null ? false : args.serverResponse.GetResponseHeader("transfer-encoding").ToLower().Contains("chunked") ? true : false;
var contentEncoding = args.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-encoding"); var contentEncoding = args.serverResponse.ContentEncoding;
if (contentEncoding != null) if (contentEncoding != null)
switch (contentEncoding.Value.ToLower()) switch (contentEncoding.ToLower())
{ {
case "gzip": case "gzip":
args.responseBody = CompressionHelper.CompressGzip(args.responseBody); args.responseBody = CompressionHelper.CompressGzip(args.responseBody);
...@@ -68,20 +68,20 @@ namespace Titanium.Web.Proxy ...@@ -68,20 +68,20 @@ namespace Titanium.Web.Proxy
args.responseBody = CompressionHelper.CompressZlib(args.responseBody); args.responseBody = CompressionHelper.CompressZlib(args.responseBody);
break; break;
default: default:
throw new Exception("Specified content-encoding header is not supported"); break;
} }
WriteResponseStatus(args.serverResponse.ProtocolVersion, args.serverResponse.StatusCode, args.serverResponse.StatusDescription, args.clientStreamWriter); WriteResponseStatus(args.serverResponse.ProtocolVersion, args.serverResponse.StatusCode, args.serverResponse.StatusDescription, args.clientStreamWriter);
WriteResponseHeaders(args.clientStreamWriter, args.ResponseHeaders, args.responseBody.Length, isChunked); WriteResponseHeaders(args.clientStreamWriter, args.responseHeaders, args.responseBody.Length, isChunked);
WriteResponseBody(args.clientStream, args.responseBody, isChunked); WriteResponseBody(args.clientStream, args.responseBody, isChunked);
} }
else else
{ {
bool isChunked = args.serverResponse.GetResponseHeader("transfer-encoding") == null ? false : args.serverResponse.GetResponseHeader("transfer-encoding").ToLower() == "chunked" ? true : false; bool isChunked = args.serverResponse.GetResponseHeader("transfer-encoding") == null ? false : args.serverResponse.GetResponseHeader("transfer-encoding").ToLower().Contains("chunked") ? true : false;
WriteResponseStatus(args.serverResponse.ProtocolVersion, args.serverResponse.StatusCode, args.serverResponse.StatusDescription, args.clientStreamWriter); WriteResponseStatus(args.serverResponse.ProtocolVersion, args.serverResponse.StatusCode, args.serverResponse.StatusDescription, args.clientStreamWriter);
WriteResponseHeaders(args.clientStreamWriter, args.ResponseHeaders); WriteResponseHeaders(args.clientStreamWriter, args.responseHeaders);
WriteResponseBody(args.responseStream, args.clientStream, isChunked); WriteResponseBody(args.responseStream, args.clientStream, isChunked);
} }
......
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