Commit 2d880884 authored by titanium007's avatar titanium007

Fix POST chunked file upload

parent c084ccb6
......@@ -57,9 +57,9 @@ Sample request and response event handlers
{
Console.WriteLine(e.RequestURL);
Console.WriteLine(e.RequestURL);
//modify request headers
//read request headers
var requestHeaders = e.RequestHeaders;
if ((e.RequestMethod.ToUpper() == "POST" || e.RequestMethod.ToUpper() == "PUT") && e.RequestContentLength > 0)
......@@ -87,9 +87,10 @@ Sample request and response event handlers
public void OnResponse(object sender, SessionEventArgs e)
{
//modify response headers
//read response headers
var responseHeaders = e.ResponseHeaders;
if (e.ResponseStatusCode == HttpStatusCode.OK)
{
if (e.ResponseContentType.Trim().ToLower().Contains("text/html"))
......@@ -101,7 +102,7 @@ Sample request and response event handlers
//Get response body as string
string responseBody = e.GetResponseBodyAsString();
//Inject script in to body
//Modify e.ServerResponse
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);
......
......@@ -60,10 +60,10 @@ namespace Titanium.Web.Proxy.Test
Console.WriteLine(e.RequestURL);
////modify request headers
////read request headers
//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
// byte[] bodyBytes = e.GetRequestBody();
......@@ -89,9 +89,9 @@ namespace Titanium.Web.Proxy.Test
//Insert script to read the Browser URL and send it back to proxy
public void OnResponse(object sender, SessionEventArgs e)
{
////modify response headers
////read response headers
//var responseHeaders = e.ResponseHeaders;
//if (e.ResponseStatusCode == HttpStatusCode.OK)
//{
......
......@@ -21,6 +21,14 @@ namespace Titanium.Web.Proxy.EventArguments
internal CustomBinaryReader clientStreamReader { 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 Version requestHttpVersion { get; set; }
internal bool requestIsAlive { get; set; }
......@@ -28,25 +36,19 @@ namespace Titanium.Web.Proxy.EventArguments
internal byte[] requestBody { get; set; }
internal string requestBodyString { 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 Stream responseStream { get; set; }
internal byte[] responseBody { get; set; }
internal string responseBodyString { get; set; }
internal bool responseBodyRead { get; set; }
internal bool RequestLocked { get; set; }
internal HttpWebRequest proxyRequest { get; set; }
internal List<HttpHeader> responseHeaders { get; set; }
internal bool ResponseLocked { 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 IPAddress ClientIpAddress { get { return this.clientIpAddress; } }
......@@ -56,14 +58,17 @@ namespace Titanium.Web.Proxy.EventArguments
public string RequestURL { get { return this.requestURL; } }
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
{
get
{
if (this.RequestHeaders.Any(x => x.Name.ToLower() == "content-length"))
if (this.requestHeaders.Any(x => x.Name.ToLower() == "content-length"))
{
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)
return contentLen;
}
......@@ -72,11 +77,11 @@ namespace Titanium.Web.Proxy.EventArguments
}
public string RequestMethod { get { return this.proxyRequest.Method; } }
public List<HttpHeader> RequestHeaders { get; set; }
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 List<HttpHeader> ResponseHeaders { get; set; }
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; } }
internal SessionEventArgs(int bufferSize)
{
......@@ -85,9 +90,88 @@ namespace Titanium.Web.Proxy.EventArguments
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)
{
requestBody = clientStreamReader.ReadBytes(RequestContentLength);
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);
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;
}
......@@ -95,6 +179,7 @@ namespace Titanium.Web.Proxy.EventArguments
{
if (responseBody == null)
{
switch (serverResponse.ContentEncoding)
{
case "gzip":
......@@ -144,34 +229,25 @@ namespace Titanium.Web.Proxy.EventArguments
{
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()
{
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();
if (requestBodyString == null)
{
requestBodyString = requestEncoding.GetString(requestBody);
}
return requestBodyString;
readRequestBody();
if (requestBodyString == null)
{
requestBodyString = requestEncoding.GetString(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.");
return requestBodyString;
}
......@@ -302,7 +378,6 @@ namespace Titanium.Web.Proxy.EventArguments
}
}
}
\ No newline at end of file
......@@ -59,7 +59,7 @@ namespace Titanium.Web.Proxy.Helpers
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;
......
......@@ -50,7 +50,7 @@ namespace Titanium.Web.Proxy
httpRemoteUri = new Uri(httpCmdSplit[1]);
var httpVersion = httpCmdSplit[2];
var excluded = ExcludedHttpsHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x));
//Client wants to create a secure tcp tunnel (its a HTTPS request)
......@@ -59,11 +59,11 @@ namespace Titanium.Web.Proxy
httpRemoteUri = new Uri("https://" + httpCmdSplit[1]);
clientStreamReader.ReadAllLines();
WriteConnectResponse(clientStreamWriter, httpVersion);
var certificate = ProxyServer.CertManager.CreateCertificate(httpRemoteUri.Host);
SslStream sslStream = null;
try
......@@ -112,7 +112,7 @@ namespace Titanium.Web.Proxy
}
private static void HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream, CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string secureTunnelHostName)
{
......@@ -124,7 +124,7 @@ namespace Titanium.Web.Proxy
var args = new SessionEventArgs(BUFFER_SIZE);
args.client = client;
try
{
......@@ -150,26 +150,26 @@ namespace Titanium.Web.Proxy
args.isHttps = true;
}
args.RequestHeaders = new List<HttpHeader>();
args.requestHeaders = new List<HttpHeader>();
string tmpLine = null;
while (!String.IsNullOrEmpty(tmpLine = clientStreamReader.ReadLine()))
{
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 ((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);
return;
}
......@@ -184,7 +184,7 @@ namespace Titanium.Web.Proxy
args.proxyRequest.ProtocolVersion = version;
args.clientStream = clientStream;
args.clientStreamReader = clientStreamReader;
args.clientStreamWriter = clientStreamWriter;
args.clientStreamWriter = clientStreamWriter;
args.proxyRequest.AllowAutoRedirect = false;
args.proxyRequest.AutomaticDecompression = DecompressionMethods.None;
args.requestHostname = args.proxyRequest.RequestUri.Host;
......@@ -195,7 +195,7 @@ namespace Titanium.Web.Proxy
args.requestIsAlive = args.proxyRequest.KeepAlive;
args.proxyRequest.ConnectionGroupName = args.requestHostname;
args.proxyRequest.AllowWriteStreamBuffering = true;
//If requested interception
if (BeforeRequest != null)
......@@ -212,7 +212,7 @@ namespace Titanium.Web.Proxy
return;
}
SetRequestHeaders(args.RequestHeaders, args.proxyRequest);
SetRequestHeaders(args.requestHeaders, args.proxyRequest);
//If request was modified by user
if (args.requestBodyRead)
......@@ -230,7 +230,6 @@ namespace Titanium.Web.Proxy
if (httpMethod.ToUpper() == "POST" || httpMethod.ToUpper() == "PUT")
{
SendClientRequestBody(args);
}
//Http request body sent, now wait asynchronously for response
args.proxyRequest.BeginGetResponse(new AsyncCallback(HandleHttpSessionResponse), args);
......@@ -267,79 +266,80 @@ namespace Titanium.Web.Proxy
for (int i = 0; i < requestHeaders.Count; i++)
{
switch (requestHeaders[i].Name.ToLower())
{
case "accept":
webRequest.Accept = requestHeaders[i].Value;
break;
case "accept-encoding":
webRequest.Headers.Add("Accept-Encoding", "gzip,deflate,zlib");
break;
case "cookie":
webRequest.Headers["Cookie"] = requestHeaders[i].Value;
break;
case "connection":
if (requestHeaders[i].Value.ToLower() == "keep-alive")
webRequest.KeepAlive = true;
break;
case "content-length":
int contentLen;
int.TryParse(requestHeaders[i].Value, out contentLen);
if (contentLen != 0)
webRequest.ContentLength = contentLen;
break;
case "content-type":
webRequest.ContentType = requestHeaders[i].Value;
break;
case "expect":
if (requestHeaders[i].Value.ToLower() == "100-continue")
webRequest.ServicePoint.Expect100Continue = true;
else
webRequest.Expect = requestHeaders[i].Value;
break;
case "host":
webRequest.Host = requestHeaders[i].Value;
break;
case "if-modified-since":
String[] sb = requestHeaders[i].Value.Trim().Split(semiSplit);
DateTime d;
if (DateTime.TryParse(sb[0], out d))
webRequest.IfModifiedSince = d;
break;
case "proxy-connection":
if (requestHeaders[i].Value.ToLower() == "keep-alive")
webRequest.KeepAlive = true;
break;
case "range":
var startEnd = requestHeaders[i].Value.Replace(Environment.NewLine, "").Remove(0, 6).Split('-');
if (startEnd.Length > 1) { if (!String.IsNullOrEmpty(startEnd[1])) webRequest.AddRange(int.Parse(startEnd[0]), int.Parse(startEnd[1])); else webRequest.AddRange(int.Parse(startEnd[0])); }
else
webRequest.AddRange(int.Parse(startEnd[0]));
break;
case "referer":
webRequest.Referer = requestHeaders[i].Value;
break;
case "user-agent":
webRequest.UserAgent = requestHeaders[i].Value;
break;
//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?
case "transfer-encoding":
if (requestHeaders[i].Value.ToLower() == "chunked")
webRequest.SendChunked = true;
else
webRequest.SendChunked = false;
break;
case "upgrade":
if (requestHeaders[i].Value.ToLower() == "http/1.1")
webRequest.Headers.Add("Upgrade", requestHeaders[i].Value);
break;
default:
webRequest.Headers.Add(requestHeaders[i].Name, requestHeaders[i].Value);
break;
}
{
case "accept":
webRequest.Accept = requestHeaders[i].Value;
break;
case "accept-encoding":
webRequest.Headers.Add("Accept-Encoding", "gzip,deflate,zlib");
break;
case "cookie":
webRequest.Headers["Cookie"] = requestHeaders[i].Value;
break;
case "connection":
if (requestHeaders[i].Value.ToLower() == "keep-alive")
webRequest.KeepAlive = true;
break;
case "content-length":
int contentLen;
int.TryParse(requestHeaders[i].Value, out contentLen);
if (contentLen != 0)
webRequest.ContentLength = contentLen;
break;
case "content-type":
webRequest.ContentType = requestHeaders[i].Value;
break;
case "expect":
if (requestHeaders[i].Value.ToLower() == "100-continue")
webRequest.ServicePoint.Expect100Continue = true;
else
webRequest.Expect = requestHeaders[i].Value;
break;
case "host":
webRequest.Host = requestHeaders[i].Value;
break;
case "if-modified-since":
String[] sb = requestHeaders[i].Value.Trim().Split(semiSplit);
DateTime d;
if (DateTime.TryParse(sb[0], out d))
webRequest.IfModifiedSince = d;
break;
case "proxy-connection":
if (requestHeaders[i].Value.ToLower() == "keep-alive")
webRequest.KeepAlive = true;
break;
case "range":
var startEnd = requestHeaders[i].Value.Replace(Environment.NewLine, "").Remove(0, 6).Split('-');
if (startEnd.Length > 1) { if (!String.IsNullOrEmpty(startEnd[1])) webRequest.AddRange(int.Parse(startEnd[0]), int.Parse(startEnd[1])); else webRequest.AddRange(int.Parse(startEnd[0])); }
else
webRequest.AddRange(int.Parse(startEnd[0]));
break;
case "referer":
webRequest.Referer = requestHeaders[i].Value;
break;
case "user-agent":
webRequest.UserAgent = requestHeaders[i].Value;
break;
//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?
case "transfer-encoding":
if (requestHeaders[i].Value.ToLower().Contains("chunked"))
webRequest.SendChunked = true;
else
webRequest.SendChunked = false;
break;
case "upgrade":
if (requestHeaders[i].Value.ToLower() == "http/1.1")
webRequest.Headers.Add("Upgrade", requestHeaders[i].Value);
break;
default:
webRequest.Headers.Add(requestHeaders[i].Name, requestHeaders[i].Value);
break;
}
}
......@@ -357,7 +357,7 @@ namespace Titanium.Web.Proxy
args.proxyRequest.AllowWriteStreamBuffering = true;
try
{
int totalbytesRead = 0;
int bytesToRead;
......@@ -401,62 +401,31 @@ namespace Titanium.Web.Proxy
else if (args.proxyRequest.SendChunked)
{
args.proxyRequest.AllowWriteStreamBuffering = true;
try
{
StringBuilder sb = new StringBuilder();
byte[] byteRead = new byte[1];
while (true)
{
var chuchkHead = args.clientStreamReader.ReadLine();
var chunkSize = int.Parse(chuchkHead, System.Globalization.NumberStyles.HexNumber);
args.clientStream.Read(byteRead, 0, 1);
sb.Append(Encoding.ASCII.GetString(byteRead));
if (chunkSize != 0)
{
var buffer = args.clientStreamReader.ReadBytes(chunkSize);
postStream.Write(buffer, 0, buffer.Length);
if (sb.ToString().EndsWith(Environment.NewLine))
var chunkTrail = args.clientStreamReader.ReadLine();
}
else
{
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++)
{
args.clientStream.ReadByte();
}
break;
}
var totalbytesRead = 0;
int bytesToRead;
if (chunckSize < BUFFER_SIZE)
{
bytesToRead = chunckSize;
}
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;
}
postStream.Write(buffer, 0, buffer.Length);
}
for (int i = 0; i < Encoding.ASCII.GetByteCount(Environment.NewLine); i++)
{
args.clientStream.ReadByte();
}
sb.Clear();
args.clientStreamReader.ReadLine();
break;
}
}
postStream.Close();
}
catch
......@@ -474,6 +443,6 @@ namespace Titanium.Web.Proxy
}
}
}
\ No newline at end of file
......@@ -38,10 +38,10 @@ namespace Titanium.Web.Proxy
{
if (args.serverResponse != null)
{
args.ResponseHeaders = ReadResponseHeaders(args.serverResponse);
args.responseHeaders = ReadResponseHeaders(args.serverResponse);
args.responseStream = args.serverResponse.GetResponseStream();
if (BeforeResponse != null)
{
args.responseEncoding = args.serverResponse.GetEncoding();
......@@ -52,11 +52,11 @@ namespace Titanium.Web.Proxy
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;
var contentEncoding = args.ResponseHeaders.FirstOrDefault(x => x.Name.ToLower() == "content-encoding");
bool isChunked = args.serverResponse.GetResponseHeader("transfer-encoding") == null ? false : args.serverResponse.GetResponseHeader("transfer-encoding").ToLower().Contains("chunked") ? true : false;
var contentEncoding = args.serverResponse.ContentEncoding;
if (contentEncoding != null)
switch (contentEncoding.Value.ToLower())
switch (contentEncoding.ToLower())
{
case "gzip":
args.responseBody = CompressionHelper.CompressGzip(args.responseBody);
......@@ -68,20 +68,20 @@ namespace Titanium.Web.Proxy
args.responseBody = CompressionHelper.CompressZlib(args.responseBody);
break;
default:
throw new Exception("Specified content-encoding header is not supported");
break;
}
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);
}
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);
WriteResponseHeaders(args.clientStreamWriter, args.ResponseHeaders);
WriteResponseHeaders(args.clientStreamWriter, args.responseHeaders);
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