Commit e888c006 authored by titanium007's avatar titanium007

Merge pull request #28 from titanium007/breaking-change

Breaking change
parents 0cc53a6e cff7a937
......@@ -23,6 +23,10 @@ if(!$Configuration) { $Configuration = "Release" }
if(!$Version) { $Version = $env:APPVEYOR_BUILD_VERSION }
if(!$Version) { $Version = "1.0.$BuildNumber" }
if(!$Branch) { $Branch = $env:APPVEYOR_REPO_BRANCH }
if(!$Branch) { $Branch = "local" }
if($Branch -eq "release" ) { $Version = "$Version-beta" }
Import-Module "$Here\Common" -DisableNameChecking
$NuGet = Join-Path $SolutionRoot ".nuget\nuget.exe"
......
......@@ -58,12 +58,12 @@ Sample request and response event handlers
//Test On Request, intercept requests
public void OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine(e.RequestURL);
Console.WriteLine(e.ProxySession.Request.RequestUrl);
//read request headers
var requestHeaders = e.RequestHeaders;
var requestHeaders = e.ProxySession.Request.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();
......@@ -78,7 +78,7 @@ Sample request and response event handlers
//To cancel a request with a custom HTML content
//Filter URL
if (e.RequestURL.Contains("google.com"))
if (e.ProxySession.Request.RequestUrl.Contains("google.com"))
{
e.Ok("<!DOCTYPE html><html><body><h1>Website Blocked</h1><p>Blocked by titanium web proxy.</p></body></html>");
}
......@@ -86,10 +86,11 @@ Sample request and response event handlers
public void OnResponse(object sender, SessionEventArgs e)
{
//read response headers
var responseHeaders = e.ResponseHeaders;
////read response headers
var responseHeaders = e.ProxySession.Response.ResponseHeaders;
if (e.ResponseStatusCode == HttpStatusCode.OK)
if (e.ResponseStatusCode == "200")
{
if (e.ResponseContentType.Trim().ToLower().Contains("text/html"))
{
......
......@@ -4,4 +4,17 @@
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0,Profile=Client" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.6.10.0" newVersion="2.6.10.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.6.10.0" newVersion="2.6.10.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
\ No newline at end of file
using System;
using System.Text.RegularExpressions;
using Titanium.Web.Proxy.EventArguments;
namespace Titanium.Web.Proxy.Test
......@@ -43,10 +44,10 @@ namespace Titanium.Web.Proxy.Test
//Read browser URL send back to proxy by the injection script in OnResponse event
public void OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine(e.RequestUrl);
Console.WriteLine(e.ProxySession.Request.RequestUrl);
////read request headers
//var requestHeaders = e.RequestHeaders;
//var requestHeaders = e.ProxySession.Request.RequestHeaders;
//if ((e.RequestMethod.ToUpper() == "POST" || e.RequestMethod.ToUpper() == "PUT"))
//{
......@@ -63,7 +64,7 @@ namespace Titanium.Web.Proxy.Test
////To cancel a request with a custom HTML content
////Filter URL
//if (e.RequestURL.Contains("google.com"))
//if (e.ProxySession.Request.RequestUrl.Contains("google.com"))
//{
// e.Ok("<!DOCTYPE html><html><body><h1>Website Blocked</h1><p>Blocked by titanium web proxy.</p></body></html>");
//}
......@@ -74,10 +75,10 @@ namespace Titanium.Web.Proxy.Test
public void OnResponse(object sender, SessionEventArgs e)
{
////read response headers
//var responseHeaders = e.ResponseHeaders;
var responseHeaders = e.ProxySession.Response.ResponseHeaders;
//if (e.ResponseStatusCode == HttpStatusCode.OK)
//if (e.ResponseStatusCode == "200")
//{
// if (e.ResponseContentType.Trim().ToLower().Contains("text/html"))
// {
......
......@@ -8,10 +8,22 @@ using System.Net.Sockets;
using System.Text;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.EventArguments
{
public class Client
{
internal TcpClient TcpClient { get; set; }
internal Stream ClientStream { get; set; }
internal CustomBinaryReader ClientStreamReader { get; set; }
internal StreamWriter ClientStreamWriter { get; set; }
public int ClientPort { get; internal set; }
public IPAddress ClientIpAddress { get; internal set; }
}
public class SessionEventArgs : EventArgs, IDisposable
{
readonly int _bufferSize;
......@@ -19,48 +31,25 @@ namespace Titanium.Web.Proxy.EventArguments
internal SessionEventArgs(int bufferSize)
{
_bufferSize = bufferSize;
Client = new Client();
ProxySession = new HttpWebSession();
}
internal TcpClient Client { get; set; }
internal Stream ClientStream { get; set; }
internal CustomBinaryReader ClientStreamReader { get; set; }
internal StreamWriter ClientStreamWriter { get; set; }
public Client Client { get; set; }
public bool IsHttps { get; internal set; }
public string RequestUrl { get; internal set; }
public string RequestHostname { get; internal set; }
public int ClientPort { get; internal set; }
public IPAddress ClientIpAddress { get; internal set; }
public HttpWebSession ProxySession { get; set; }
internal Encoding RequestEncoding { get; set; }
internal Version RequestHttpVersion { get; set; }
internal bool RequestIsAlive { get; set; }
internal bool CancelRequest { get; set; }
internal byte[] RequestBody { get; set; }
internal string RequestBodyString { get; set; }
internal bool RequestBodyRead { get; set; }
public List<HttpHeader> RequestHeaders { get; internal 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; }
public List<HttpHeader> ResponseHeaders { get; internal set; }
internal bool ResponseLocked { get; set; }
internal HttpWebResponse ServerResponse { get; set; }
public int RequestContentLength
{
get
{
if (RequestHeaders.All(x => x.Name.ToLower() != "content-length")) return -1;
if (ProxySession.Request.RequestHeaders.All(x => x.Name.ToLower() != "content-length")) return -1;
int contentLen;
int.TryParse(RequestHeaders.First(x => x.Name.ToLower() == "content-length").Value, out contentLen);
int.TryParse(ProxySession.Request.RequestHeaders.First(x => x.Name.ToLower() == "content-length").Value, out contentLen);
if (contentLen != 0)
return contentLen;
return -1;
......@@ -69,60 +58,53 @@ namespace Titanium.Web.Proxy.EventArguments
public string RequestMethod
{
get { return ProxyRequest.Method; }
get { return ProxySession.Request.Method; }
}
public HttpStatusCode ResponseStatusCode
public string ResponseStatusCode
{
get { return ServerResponse.StatusCode; }
get { return ProxySession.Response.ResponseStatusCode; }
}
public string ResponseContentType
{
get
{
return ResponseHeaders.Any(x => x.Name.ToLower() == "content-type")
? ResponseHeaders.First(x => x.Name.ToLower() == "content-type").Value
return ProxySession.Response.ResponseHeaders.Any(x => x.Name.ToLower() == "content-type")
? ProxySession.Response.ResponseHeaders.First(x => x.Name.ToLower() == "content-type").Value
: null;
}
}
public void Dispose()
{
if (ProxyRequest != null)
ProxyRequest.Abort();
if (ResponseStream != null)
ResponseStream.Dispose();
if (ServerResponse != null)
ServerResponse.Close();
}
private void ReadRequestBody()
{
if ((ProxyRequest.Method.ToUpper() != "POST" && ProxyRequest.Method.ToUpper() != "PUT"))
if ((ProxySession.Request.Method.ToUpper() != "POST" && ProxySession.Request.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 (ProxySession.Request.RequestBody == null)
{
var isChunked = false;
string requestContentEncoding = null;
if (RequestHeaders.Any(x => x.Name.ToLower() == "content-encoding"))
if (ProxySession.Request.RequestHeaders.Any(x => x.Name.ToLower() == "content-encoding"))
{
requestContentEncoding = RequestHeaders.First(x => x.Name.ToLower() == "content-encoding").Value;
requestContentEncoding = ProxySession.Request.RequestHeaders.First(x => x.Name.ToLower() == "content-encoding").Value;
}
if (RequestHeaders.Any(x => x.Name.ToLower() == "transfer-encoding"))
if (ProxySession.Request.RequestHeaders.Any(x => x.Name.ToLower() == "transfer-encoding"))
{
var transferEncoding =
RequestHeaders.First(x => x.Name.ToLower() == "transfer-encoding").Value.ToLower();
ProxySession.Request.RequestHeaders.First(x => x.Name.ToLower() == "transfer-encoding").Value.ToLower();
if (transferEncoding.Contains("chunked"))
{
isChunked = true;
......@@ -131,7 +113,7 @@ namespace Titanium.Web.Proxy.EventArguments
if (requestContentEncoding == null && !isChunked)
RequestBody = ClientStreamReader.ReadBytes(RequestContentLength);
ProxySession.Request.RequestBody = this.Client.ClientStreamReader.ReadBytes(RequestContentLength);
else
{
using (var requestBodyStream = new MemoryStream())
......@@ -140,203 +122,220 @@ namespace Titanium.Web.Proxy.EventArguments
{
while (true)
{
var chuchkHead = ClientStreamReader.ReadLine();
var chuchkHead = this.Client.ClientStreamReader.ReadLine();
var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);
if (chunkSize != 0)
{
var buffer = ClientStreamReader.ReadBytes(chunkSize);
var buffer = this.Client.ClientStreamReader.ReadBytes(chunkSize);
requestBodyStream.Write(buffer, 0, buffer.Length);
//chunk trail
ClientStreamReader.ReadLine();
this.Client.ClientStreamReader.ReadLine();
}
else
{
ClientStreamReader.ReadLine();
this.Client.ClientStreamReader.ReadLine();
break;
}
}
}
try
{
switch (requestContentEncoding)
{
case "gzip":
RequestBody = CompressionHelper.DecompressGzip(requestBodyStream);
ProxySession.Request.RequestBody = CompressionHelper.DecompressGzip(requestBodyStream.ToArray());
break;
case "deflate":
RequestBody = CompressionHelper.DecompressDeflate(requestBodyStream);
ProxySession.Request.RequestBody = CompressionHelper.DecompressDeflate(requestBodyStream);
break;
case "zlib":
RequestBody = CompressionHelper.DecompressZlib(requestBodyStream);
ProxySession.Request.RequestBody = CompressionHelper.DecompressZlib(requestBodyStream);
break;
default:
RequestBody = requestBodyStream.ToArray();
ProxySession.Request.RequestBody = requestBodyStream.ToArray();
break;
}
}
catch
{
RequestBody = requestBodyStream.ToArray();
ProxySession.Request.RequestBody = requestBodyStream.ToArray();
}
}
}
}
RequestBodyRead = true;
ProxySession.Request.RequestBodyRead = true;
}
private void ReadResponseBody()
{
if (ResponseBody == null)
if (ProxySession.Response.ResponseBody == null)
{
switch (ServerResponse.ContentEncoding)
using (var responseBodyStream = new MemoryStream())
{
case "gzip":
ResponseBody = CompressionHelper.DecompressGzip(ResponseStream);
break;
case "deflate":
ResponseBody = CompressionHelper.DecompressDeflate(ResponseStream);
break;
case "zlib":
ResponseBody = CompressionHelper.DecompressZlib(ResponseStream);
break;
default:
ResponseBody = DecodeData(ResponseStream);
break;
if (ProxySession.Response.IsChunked)
{
while (true)
{
var chuchkHead = ProxySession.ProxyClient.ServerStreamReader.ReadLine();
var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);
if (chunkSize != 0)
{
var buffer = ProxySession.ProxyClient.ServerStreamReader.ReadBytes(chunkSize);
responseBodyStream.Write(buffer, 0, buffer.Length);
//chunk trail
ProxySession.ProxyClient.ServerStreamReader.ReadLine();
}
else
{
ProxySession.ProxyClient.ServerStreamReader.ReadLine();
break;
}
}
}
else
{
var buffer = ProxySession.ProxyClient.ServerStreamReader.ReadBytes(ProxySession.Response.ContentLength);
responseBodyStream.Write(buffer, 0, buffer.Length);
}
switch (ProxySession.Response.ResponseContentEncoding)
{
case "gzip":
ProxySession.Response.ResponseBody = CompressionHelper.DecompressGzip(responseBodyStream.ToArray());
break;
case "deflate":
ProxySession.Response.ResponseBody = CompressionHelper.DecompressDeflate(responseBodyStream);
break;
case "zlib":
ProxySession.Response.ResponseBody = CompressionHelper.DecompressZlib(responseBodyStream);
break;
default:
ProxySession.Response.ResponseBody = responseBodyStream.ToArray();
break;
}
}
ResponseBodyRead = true;
ProxySession.Response.ResponseBodyRead = true;
}
}
//stream reader not recomended for images
private byte[] DecodeData(Stream responseStream)
{
var buffer = new byte[_bufferSize];
using (var ms = new MemoryStream())
{
int read;
while ((read = responseStream.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
return ms.ToArray();
}
}
public Encoding GetRequestBodyEncoding()
{
if (RequestLocked) throw new Exception("You cannot call this function after request is made to server.");
if (ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function after request is made to server.");
return RequestEncoding;
return ProxySession.Request.RequestEncoding;
}
public byte[] GetRequestBody()
{
if (RequestLocked) throw new Exception("You cannot call this function after request is made to server.");
if (ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function after request is made to server.");
ReadRequestBody();
return RequestBody;
return ProxySession.Request.RequestBody;
}
public string GetRequestBodyAsString()
{
if (RequestLocked) throw new Exception("You cannot call this function after request is made to server.");
if (ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function after request is made to server.");
ReadRequestBody();
return RequestBodyString ?? (RequestBodyString = RequestEncoding.GetString(RequestBody));
return ProxySession.Request.RequestBodyString ?? (ProxySession.Request.RequestBodyString = ProxySession.Request.RequestEncoding.GetString(ProxySession.Request.RequestBody));
}
public void SetRequestBody(byte[] body)
{
if (RequestLocked) throw new Exception("You cannot call this function after request is made to server.");
if (ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function after request is made to server.");
if (!RequestBodyRead)
if (!ProxySession.Request.RequestBodyRead)
{
ReadRequestBody();
}
RequestBody = body;
RequestBodyRead = true;
ProxySession.Request.RequestBody = body;
ProxySession.Request.RequestBodyRead = true;
}
public void SetRequestBodyString(string body)
{
if (RequestLocked) throw new Exception("You cannot call this function after request is made to server.");
if (ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function after request is made to server.");
if (!RequestBodyRead)
if (!ProxySession.Request.RequestBodyRead)
{
ReadRequestBody();
}
RequestBody = RequestEncoding.GetBytes(body);
RequestBodyRead = true;
ProxySession.Request.RequestBody = ProxySession.Request.RequestEncoding.GetBytes(body);
ProxySession.Request.RequestBodyRead = true;
}
public Encoding GetResponseBodyEncoding()
{
if (!RequestLocked) throw new Exception("You cannot call this function before request is made to server.");
if (!ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function before request is made to server.");
return ResponseEncoding;
return ProxySession.Response.ResponseEncoding;
}
public byte[] GetResponseBody()
{
if (!RequestLocked) throw new Exception("You cannot call this function before request is made to server.");
if (!ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function before request is made to server.");
ReadResponseBody();
return ResponseBody;
return ProxySession.Response.ResponseBody;
}
public string GetResponseBodyAsString()
{
if (!RequestLocked) throw new Exception("You cannot call this function before request is made to server.");
if (!ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function before request is made to server.");
GetResponseBody();
return ResponseBodyString ?? (ResponseBodyString = ResponseEncoding.GetString(ResponseBody));
return ProxySession.Response.ResponseBodyString ?? (ProxySession.Response.ResponseBodyString = ProxySession.Response.ResponseEncoding.GetString(ProxySession.Response.ResponseBody));
}
public void SetResponseBody(byte[] body)
{
if (!RequestLocked) throw new Exception("You cannot call this function before request is made to server.");
if (!ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function before request is made to server.");
if (ResponseBody == null)
if (ProxySession.Response.ResponseBody == null)
{
GetResponseBody();
}
ResponseBody = body;
ProxySession.Response.ResponseBody = body;
}
public void SetResponseBodyString(string body)
{
if (!RequestLocked) throw new Exception("You cannot call this function before request is made to server.");
if (!ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function before request is made to server.");
if (ResponseBody == null)
if (ProxySession.Response.ResponseBody == null)
{
GetResponseBody();
}
var bodyBytes = ResponseEncoding.GetBytes(body);
var bodyBytes = ProxySession.Response.ResponseEncoding.GetBytes(body);
SetResponseBody(bodyBytes);
}
public void Ok(string html)
{
if (RequestLocked) throw new Exception("You cannot call this function after request is made to server.");
if (ProxySession.Request.RequestLocked) throw new Exception("You cannot call this function after request is made to server.");
if (html == null)
html = string.Empty;
var result = Encoding.Default.GetBytes(html);
var connectStreamWriter = new StreamWriter(ClientStream);
var s = string.Format("HTTP/{0}.{1} {2} {3}", RequestHttpVersion.Major, RequestHttpVersion.Minor, 200, "Ok");
var connectStreamWriter = new StreamWriter(this.Client.ClientStream);
var s = string.Format("HTTP/{0}.{1} {2} {3}", ProxySession.Request.RequestHttpVersion.Major, ProxySession.Request.RequestHttpVersion.Minor, 200, "Ok");
connectStreamWriter.WriteLine(s);
connectStreamWriter.WriteLine("Timestamp: {0}", DateTime.Now);
connectStreamWriter.WriteLine("content-length: " + result.Length);
......@@ -344,15 +343,15 @@ namespace Titanium.Web.Proxy.EventArguments
connectStreamWriter.WriteLine("Pragma: no-cache");
connectStreamWriter.WriteLine("Expires: 0");
connectStreamWriter.WriteLine(RequestIsAlive ? "Connection: Keep-Alive" : "Connection: close");
connectStreamWriter.WriteLine(ProxySession.Request.RequestIsAlive ? "Connection: Keep-Alive" : "Connection: close");
connectStreamWriter.WriteLine();
connectStreamWriter.Flush();
ClientStream.Write(result, 0, result.Length);
this.Client.ClientStream.Write(result, 0, result.Length);
CancelRequest = true;
ProxySession.Request.CancelRequest = true;
}
}
}
\ No newline at end of file
using System.Net;
using System.Text;
using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.Extensions
{
public static class HttpWebRequestExtensions
{
public static Encoding GetEncoding(this HttpWebRequest request)
public static Encoding GetEncoding(this HttpWebSession request)
{
try
{
if (request.ContentType == null) return Encoding.GetEncoding("ISO-8859-1");
if (request.Request.RequestContentType == null) return Encoding.GetEncoding("ISO-8859-1");
var contentTypes = request.ContentType.Split(';');
var contentTypes = request.Request.RequestContentType.Split(';');
foreach (var contentType in contentTypes)
{
var encodingSplit = contentType.Split('=');
......
using System.Net;
using System.Text;
using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.Extensions
{
public static class HttpWebResponseExtensions
{
public static Encoding GetEncoding(this HttpWebResponse response)
public static Encoding GetResponseEncoding(this HttpWebSession response)
{
if (string.IsNullOrEmpty(response.CharacterSet)) return Encoding.GetEncoding("ISO-8859-1");
return Encoding.GetEncoding(response.CharacterSet.Replace(@"""",string.Empty));
if (string.IsNullOrEmpty(response.Response.ResponseCharacterSet)) return Encoding.GetEncoding("ISO-8859-1");
return Encoding.GetEncoding(response.Response.ResponseCharacterSet.Replace(@"""", string.Empty));
}
}
}
\ No newline at end of file
......@@ -50,11 +50,10 @@ namespace Titanium.Web.Proxy.Helpers
}
}
public static byte[] DecompressGzip(Stream input)
//identify why passing stream instead of bytes returns empty result
public static byte[] DecompressGzip(byte[] gzip)
{
using (
var decompressor = new System.IO.Compression.GZipStream(input,
System.IO.Compression.CompressionMode.Decompress))
using (var decompressor = new System.IO.Compression.GZipStream(new MemoryStream(gzip), System.IO.Compression.CompressionMode.Decompress))
{
var buffer = new byte[BufferSize];
......
......@@ -5,7 +5,7 @@ using System.Text;
namespace Titanium.Web.Proxy.Helpers
{
internal class CustomBinaryReader : BinaryReader
public class CustomBinaryReader : BinaryReader
{
internal CustomBinaryReader(Stream stream, Encoding encoding)
: base(stream, encoding)
......
using System;
using System.Net.Configuration;
using System.Reflection;
namespace Titanium.Web.Proxy.Helpers
{
public class NetFrameworkHelper
{
//Fix bug in .Net 4.0 HttpWebRequest (don't use this for 4.5 and above)
//http://stackoverflow.com/questions/856885/httpwebrequest-to-url-with-dot-at-the-end
public static void UrlPeriodFix()
{
var getSyntax = typeof (UriParser).GetMethod("GetSyntax", BindingFlags.Static | BindingFlags.NonPublic);
var flagsField = typeof (UriParser).GetField("m_Flags", BindingFlags.Instance | BindingFlags.NonPublic);
if (getSyntax != null && flagsField != null)
{
foreach (var scheme in new[] {"http", "https"})
{
var parser = (UriParser) getSyntax.Invoke(null, new object[] {scheme});
if (parser != null)
{
var flagsValue = (int) flagsField.GetValue(parser);
if ((flagsValue & 0x1000000) != 0)
flagsField.SetValue(parser, flagsValue & ~0x1000000);
}
}
}
}
// Enable/disable useUnsafeHeaderParsing.
// See http://o2platform.wordpress.com/2010/10/20/dealing-with-the-server-committed-a-protocol-violation-sectionresponsestatusline/
public static bool ToggleAllowUnsafeHeaderParsing(bool enable)
{
//Get the assembly that contains the internal class
var assembly = Assembly.GetAssembly(typeof (SettingsSection));
if (assembly != null)
{
//Use the assembly in order to get the internal type for the internal class
var settingsSectionType = assembly.GetType("System.Net.Configuration.SettingsSectionInternal");
if (settingsSectionType != null)
{
//Use the internal static property to get an instance of the internal settings class.
//If the static instance isn't created already invoking the property will create it for us.
var anInstance = settingsSectionType.InvokeMember("Section",
BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.NonPublic, null, null,
new object[] {});
if (anInstance != null)
{
//Locate the private bool field that tells the framework if unsafe header parsing is allowed
var aUseUnsafeHeaderParsing = settingsSectionType.GetField("useUnsafeHeaderParsing",
BindingFlags.NonPublic | BindingFlags.Instance);
if (aUseUnsafeHeaderParsing != null)
{
aUseUnsafeHeaderParsing.SetValue(anInstance, enable);
return true;
}
}
}
}
return false;
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network
{
public class Request
{
public string Method { get; internal set; }
public Uri RequestUri { get; internal set; }
public string Version { get; internal set; }
public string RequestStatus { get; internal set; }
public int RequestContentLength { get; internal set; }
public bool RequestSendChunked { get; internal set; }
public string RequestContentType { get; internal set; }
public bool RequestKeepAlive { get; internal set; }
public string RequestHost { get; internal set; }
public string RequestUrl { get; internal set; }
internal Encoding RequestEncoding { get; set; }
internal Version RequestHttpVersion { get; set; }
internal bool RequestIsAlive { get; set; }
internal bool CancelRequest { get; set; }
internal byte[] RequestBody { get; set; }
internal string RequestBodyString { get; set; }
internal bool RequestBodyRead { get; set; }
internal bool UpgradeToWebSocket { get; set; }
public List<HttpHeader> RequestHeaders { get; internal set; }
internal bool RequestLocked { get; set; }
public Request()
{
this.RequestHeaders = new List<HttpHeader>();
}
}
public class Response
{
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 ResponseLocked { get; set; }
public List<HttpHeader> ResponseHeaders { get; internal set; }
internal string ResponseCharacterSet { get; set; }
internal string ResponseContentEncoding { get; set; }
internal System.Version ResponseProtocolVersion { get; set; }
internal string ResponseStatusCode { get; set; }
internal string ResponseStatusDescription { get; set; }
internal bool ResponseKeepAlive { get; set; }
internal string ResponseContentType { get; set; }
internal int ContentLength { get; set; }
internal bool IsChunked { get; set; }
public Response()
{
this.ResponseHeaders = new List<HttpHeader>();
this.ResponseKeepAlive = true;
}
}
public class HttpWebSession
{
private const string Space = " ";
public bool IsSecure
{
get
{
return this.Request.RequestUri.Scheme == Uri.UriSchemeHttps;
}
}
public Request Request { get; set; }
public Response Response { get; set; }
internal TcpConnection ProxyClient { get; set; }
public void SetConnection(TcpConnection Connection)
{
Connection.LastAccess = DateTime.Now;
ProxyClient = Connection;
}
public HttpWebSession()
{
this.Request = new Request();
this.Response = new Response();
}
public void SendRequest()
{
Stream stream = ProxyClient.Stream;
StringBuilder requestLines = new StringBuilder();
requestLines.AppendLine(string.Join(" ", new string[3]
{
this.Request.Method,
this.Request.RequestUri.PathAndQuery,
this.Request.Version
}));
foreach (HttpHeader httpHeader in this.Request.RequestHeaders)
{
requestLines.AppendLine(httpHeader.Name + ':' + httpHeader.Value);
}
requestLines.AppendLine();
string request = requestLines.ToString();
byte[] requestBytes = Encoding.ASCII.GetBytes(request);
stream.Write(requestBytes, 0, requestBytes.Length);
stream.Flush();
}
public void ReceiveResponse()
{
var httpResult = ProxyClient.ServerStreamReader.ReadLine().Split(new char[] { ' ' }, 3);
if(string.IsNullOrEmpty(httpResult[0]))
{
var s = ProxyClient.ServerStreamReader.ReadLine();
}
var httpVersion = httpResult[0];
Version version;
if (httpVersion == "HTTP/1.1")
{
version = new Version(1, 1);
}
else
{
version = new Version(1, 0);
}
this.Response.ResponseProtocolVersion = version;
this.Response.ResponseStatusCode = httpResult[1];
string status = httpResult[2];
this.Response.ResponseStatusDescription = status;
List<string> responseLines = ProxyClient.ServerStreamReader.ReadAllLines();
for (int index = 0; index < responseLines.Count; ++index)
{
string[] strArray = responseLines[index].Split(new char[] { ':' }, 2);
this.Response.ResponseHeaders.Add(new HttpHeader(strArray[0], strArray[1]));
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.IO;
using System.Net.Security;
using Titanium.Web.Proxy.Helpers;
using System.Threading;
namespace Titanium.Web.Proxy.Network
{
public class TcpConnection
{
public string HostName { get; set; }
public int port { get; set; }
public bool IsSecure { get; set; }
public TcpClient TcpClient { get; set; }
public CustomBinaryReader ServerStreamReader { get; set; }
public Stream Stream { get; set; }
public DateTime LastAccess { get; set; }
public TcpConnection()
{
LastAccess = DateTime.Now;
}
}
internal class TcpConnectionManager
{
static List<TcpConnection> ConnectionCache = new List<TcpConnection>();
public static TcpConnection GetClient(string Hostname, int port, bool IsSecure)
{
TcpConnection cached = null;
while (true)
{
lock (ConnectionCache)
{
cached = ConnectionCache.FirstOrDefault(x => x.HostName == Hostname && x.port == port && x.IsSecure == IsSecure && x.TcpClient.Connected);
if (cached != null)
ConnectionCache.Remove(cached);
}
if (cached != null && !cached.TcpClient.Client.IsConnected())
continue;
if (cached == null)
break;
}
if (cached == null)
cached = CreateClient(Hostname, port, IsSecure);
if (ConnectionCache.Where(x => x.HostName == Hostname && x.port == port && x.IsSecure == IsSecure && x.TcpClient.Connected).Count() < 2)
{
Task.Factory.StartNew(() => ReleaseClient(CreateClient(Hostname, port, IsSecure)));
}
return cached;
}
private static TcpConnection CreateClient(string Hostname, int port, bool IsSecure)
{
var client = new TcpClient(Hostname, port);
var stream = (Stream)client.GetStream();
if (IsSecure)
{
var sslStream = (SslStream)null;
try
{
sslStream = new SslStream(stream);
sslStream.AuthenticateAsClient(Hostname);
stream = (Stream)sslStream;
}
catch
{
if (sslStream != null)
sslStream.Dispose();
throw;
}
}
return new TcpConnection()
{
HostName = Hostname,
port = port,
IsSecure = IsSecure,
TcpClient = client,
ServerStreamReader = new CustomBinaryReader(stream, Encoding.ASCII),
Stream = stream
};
}
public static void ReleaseClient(TcpConnection Connection)
{
Connection.LastAccess = DateTime.Now;
ConnectionCache.Add(Connection);
}
public static void ClearIdleConnections()
{
while (true)
{
lock (ConnectionCache)
{
var cutOff = DateTime.Now.AddSeconds(-60);
ConnectionCache
.Where(x => x.LastAccess < cutOff)
.ToList()
.ForEach(x => x.TcpClient.Close());
ConnectionCache.RemoveAll(x => x.LastAccess < cutOff);
}
Thread.Sleep(1000 * 60 * 3);
}
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Network
{
internal static class TcpExtensions
{
public static bool IsConnected(this Socket client)
{
// This is how you can determine whether a socket is still connected.
bool blockingState = client.Blocking;
try
{
byte[] tmp = new byte[1];
client.Blocking = false;
client.Send(tmp, 0, 0);
return true;
}
catch (SocketException e)
{
// 10035 == WSAEWOULDBLOCK
if (e.NativeErrorCode.Equals(10035))
return true;
else
{
return false;
}
}
finally
{
client.Blocking = blockingState;
}
}
}
}
......@@ -9,6 +9,7 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy
{
......@@ -25,7 +26,7 @@ namespace Titanium.Web.Proxy
private static readonly Regex CookieSplitRegEx = new Regex(@",(?! )");
private static readonly byte[] ChunkTrail = Encoding.ASCII.GetBytes(Environment.NewLine);
private static readonly byte[] NewLineBytes = Encoding.ASCII.GetBytes(Environment.NewLine);
private static readonly byte[] ChunkEnd =
Encoding.ASCII.GetBytes(0.ToString("x2") + Environment.NewLine + Environment.NewLine);
......@@ -58,27 +59,8 @@ namespace Titanium.Web.Proxy
public static event EventHandler<SessionEventArgs> BeforeResponse;
public static void Initialize()
{
ServicePointManager.Expect100Continue = false;
WebRequest.DefaultWebProxy = null;
ServicePointManager.DefaultConnectionLimit = int.MaxValue;
ServicePointManager.DnsRefreshTimeout = 3 * 60 * 1000; //3 minutes
ServicePointManager.MaxServicePointIdleTime = 3 * 60 * 1000;
//HttpWebRequest certificate validation callback
ServicePointManager.ServerCertificateValidationCallback =
delegate(object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None) return true;
return false;
};
#if NET40
//Fix a bug in .NET 4.0
NetFrameworkHelper.UrlPeriodFix();
//useUnsafeHeaderParsing
#endif
NetFrameworkHelper.ToggleAllowUnsafeHeaderParsing(true);
{
Task.Factory.StartNew(()=>TcpConnectionManager.ClearIdleConnections());
}
......@@ -100,7 +82,10 @@ namespace Titanium.Web.Proxy
{
SystemProxyHelper.EnableProxyHttp(
Equals(ListeningIpAddress, IPAddress.Any) ? "127.0.0.1" : ListeningIpAddress.ToString(), ListeningPort);
#if !DEBUG
FireFoxHelper.AddFirefox();
#endif
if (EnableSsl)
......@@ -142,7 +127,9 @@ namespace Titanium.Web.Proxy
if (SetAsSystemProxy)
{
SystemProxyHelper.DisableAllProxy();
#if !DEBUG
FireFoxHelper.RemoveFirefox();
#endif
}
_listener.Stop();
......
......@@ -13,6 +13,7 @@ using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy
......@@ -29,6 +30,7 @@ namespace Titanium.Web.Proxy
try
{
//read the first line HTTP command
var httpCmd = clientStreamReader.ReadLine();
if (string.IsNullOrEmpty(httpCmd))
......@@ -87,7 +89,7 @@ namespace Titanium.Web.Proxy
httpCmd = clientStreamReader.ReadLine();
}
else if (httpVerb.ToUpper() == "CONNECT")
{
......@@ -113,16 +115,19 @@ namespace Titanium.Web.Proxy
private static void HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string secureTunnelHostName)
{
TcpConnection connection = null;
string lastRequestHostName = null;
while (true)
{
if (string.IsNullOrEmpty(httpCmd))
{
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
return;
break;
}
var args = new SessionEventArgs(BUFFER_SIZE);
args.Client = client;
args.Client.TcpClient = client;
try
{
......@@ -149,74 +154,73 @@ namespace Titanium.Web.Proxy
args.IsHttps = true;
}
args.RequestHeaders = new List<HttpHeader>();
string tmpLine;
args.ProxySession.Request.RequestHeaders = new List<HttpHeader>();
string tmpLine;
while (!string.IsNullOrEmpty(tmpLine = clientStreamReader.ReadLine()))
{
var header = tmpLine.Split(ColonSpaceSplit, 2, StringSplitOptions.None);
args.RequestHeaders.Add(new HttpHeader(header[0], header[1]));
var header = tmpLine.Split(new char[] { ':' }, 2);
args.ProxySession.Request.RequestHeaders.Add(new HttpHeader(header[0], header[1]));
}
for (var i = 0; i < args.RequestHeaders.Count; i++)
{
var rawHeader = args.RequestHeaders[i];
SetRequestHeaders(args.ProxySession.Request.RequestHeaders, args.ProxySession);
//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,
if (args.ProxySession.Request.UpgradeToWebSocket)
{
TcpHelper.SendRaw(clientStreamReader.BaseStream, httpCmd, args.ProxySession.Request.RequestHeaders,
httpRemoteUri.Host, httpRemoteUri.Port, httpRemoteUri.Scheme == Uri.UriSchemeHttps);
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
return;
}
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
return;
}
//construct the web request that we are going to issue on behalf of the client.
args.ProxyRequest = (HttpWebRequest)WebRequest.Create(httpRemoteUri);
args.ProxyRequest.Proxy = null;
args.ProxyRequest.UseDefaultCredentials = true;
args.ProxyRequest.Method = httpMethod;
args.ProxyRequest.ProtocolVersion = version;
args.ClientStream = clientStream;
args.ClientStreamReader = clientStreamReader;
args.ClientStreamWriter = clientStreamWriter;
args.ProxyRequest.AllowAutoRedirect = false;
args.ProxyRequest.AutomaticDecompression = DecompressionMethods.None;
args.RequestHostname = args.ProxyRequest.RequestUri.Host;
args.RequestUrl = args.ProxyRequest.RequestUri.OriginalString;
args.ClientPort = ((IPEndPoint)client.Client.RemoteEndPoint).Port;
args.ClientIpAddress = ((IPEndPoint)client.Client.RemoteEndPoint).Address;
args.RequestHttpVersion = version;
args.RequestIsAlive = args.ProxyRequest.KeepAlive;
args.ProxyRequest.AllowWriteStreamBuffering = true;
args.ProxySession.Request.RequestUri = httpRemoteUri;
args.ProxySession.Request.Method = httpMethod;
args.ProxySession.Request.Version = httpVersion;
args.Client.ClientStream = clientStream;
args.Client.ClientStreamReader = clientStreamReader;
args.Client.ClientStreamWriter = clientStreamWriter;
args.ProxySession.Request.RequestHost = args.ProxySession.Request.RequestUri.Host;
args.ProxySession.Request.RequestUrl = args.ProxySession.Request.RequestUri.OriginalString;
args.Client.ClientPort = ((IPEndPoint)client.Client.RemoteEndPoint).Port;
args.Client.ClientIpAddress = ((IPEndPoint)client.Client.RemoteEndPoint).Address;
args.ProxySession.Request.RequestHttpVersion = version;
//If requested interception
if (BeforeRequest != null)
{
args.RequestEncoding = args.ProxyRequest.GetEncoding();
args.ProxySession.Request.RequestEncoding = args.ProxySession.GetEncoding();
BeforeRequest(null, args);
}
args.RequestLocked = true;
args.ProxySession.Request.RequestLocked = true;
if (args.CancelRequest)
if (args.ProxySession.Request.CancelRequest)
{
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
return;
break;
}
SetRequestHeaders(args.RequestHeaders, args.ProxyRequest);
//construct the web request that we are going to issue on behalf of the client.
connection = connection == null ?
TcpConnectionManager.GetClient(args.ProxySession.Request.RequestUri.Host, args.ProxySession.Request.RequestUri.Port, args.IsHttps)
: lastRequestHostName != args.ProxySession.Request.RequestHost ? TcpConnectionManager.GetClient(args.ProxySession.Request.RequestUri.Host, args.ProxySession.Request.RequestUri.Port, args.IsHttps)
: connection;
lastRequestHostName = args.ProxySession.Request.RequestHost;
args.ProxySession.SetConnection(connection);
args.ProxySession.SendRequest();
//If request was modified by user
if (args.RequestBodyRead)
if (args.ProxySession.Request.RequestBodyRead)
{
args.ProxyRequest.ContentLength = args.RequestBody.Length;
var newStream = args.ProxyRequest.GetRequestStream();
newStream.Write(args.RequestBody, 0, args.RequestBody.Length);
args.ProxySession.Request.RequestContentLength = args.ProxySession.Request.RequestBody.Length;
var newStream = args.ProxySession.ProxyClient.ServerStreamReader.BaseStream;
newStream.Write(args.ProxySession.Request.RequestBody, 0, args.ProxySession.Request.RequestBody.Length);
}
else
{
......@@ -230,8 +234,9 @@ namespace Titanium.Web.Proxy
HandleHttpSessionResponse(args);
//if connection is closing exit
if (args.ResponseHeaders.Any(x => x.Name.ToLower() == "connection" && x.Value.ToLower() == "close"))
if (args.ProxySession.Response.ResponseKeepAlive == false)
{
connection.TcpClient.Close();
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
return;
}
......@@ -243,188 +248,162 @@ namespace Titanium.Web.Proxy
catch
{
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
return;
break;
}
}
if (connection != null)
TcpConnectionManager.ReleaseClient(connection);
}
private static void WriteConnectResponse(StreamWriter clientStreamWriter, string httpVersion)
{
clientStreamWriter.WriteLine(httpVersion + " 200 Connection established");
clientStreamWriter.WriteLine("Timestamp: {0}", DateTime.Now);
//clientStreamWriter.WriteLine("connection:close");
clientStreamWriter.WriteLine();
clientStreamWriter.Flush();
}
private static void SetRequestHeaders(List<HttpHeader> requestHeaders, HttpWebRequest webRequest)
private static void SetRequestHeaders(List<HttpHeader> requestHeaders, HttpWebSession webRequest)
{
for (var 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;
requestHeaders[i].Value = "gzip,deflate,zlib";
break;
case "connection":
if (requestHeaders[i].Value.ToLower() == "keep-alive")
webRequest.KeepAlive = true;
webRequest.Request.RequestKeepAlive = true;
break;
case "content-length":
int contentLen;
int.TryParse(requestHeaders[i].Value, out contentLen);
if (contentLen != 0)
webRequest.ContentLength = contentLen;
webRequest.Request.RequestContentLength = 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;
webRequest.Request.RequestContentType = requestHeaders[i].Value;
break;
case "host":
webRequest.Host = requestHeaders[i].Value;
break;
case "if-modified-since":
var sb = requestHeaders[i].Value.Trim().Split(SemiSplit);
DateTime d;
if (DateTime.TryParse(sb[0], out d))
webRequest.IfModifiedSince = d;
webRequest.Request.RequestHost = requestHeaders[i].Value;
break;
case "proxy-connection":
if (requestHeaders[i].Value.ToLower() == "keep-alive")
webRequest.KeepAlive = true;
webRequest.Request.RequestKeepAlive = true;
else if (requestHeaders[i].Value.ToLower() == "close")
webRequest.KeepAlive = false;
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]));
webRequest.Request.RequestKeepAlive = false;
break;
case "referer":
webRequest.Referer = requestHeaders[i].Value;
break;
case "user-agent":
webRequest.UserAgent = requestHeaders[i].Value;
case "upgrade":
if (requestHeaders[i].Value.ToLower() == "websocket")
webRequest.Request.UpgradeToWebSocket = true;
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;
webRequest.Request.RequestSendChunked = true;
else
webRequest.SendChunked = false;
break;
case "upgrade":
if (requestHeaders[i].Value.ToLower() == "http/1.1")
webRequest.Headers.Add("Upgrade", requestHeaders[i].Value);
webRequest.Request.RequestSendChunked = false;
break;
default:
webRequest.Headers.Add(requestHeaders[i].Name, requestHeaders[i].Value);
break;
}
}
FixRequestProxyHeaders(requestHeaders);
webRequest.Request.RequestHeaders = requestHeaders;
}
private static void FixRequestProxyHeaders(List<HttpHeader> headers)
{
//If proxy-connection close was returned inform to close the connection
var proxyHeader = headers.FirstOrDefault(x => x.Name.ToLower() == "proxy-connection");
var connectionheader = headers.FirstOrDefault(x => x.Name.ToLower() == "connection");
if (proxyHeader != null)
if (connectionheader == null)
{
headers.Add(new HttpHeader("connection", proxyHeader.Value));
}
else
{
connectionheader.Value = proxyHeader.Value;
}
headers.RemoveAll(x => x.Name.ToLower() == "proxy-connection");
}
//This is called when the request is PUT/POST to read the body
private static void SendClientRequestBody(SessionEventArgs args)
{
// End the operation
var postStream = args.ProxyRequest.GetRequestStream();
var postStream = args.ProxySession.ProxyClient.Stream;
if (args.ProxyRequest.ContentLength > 0)
if (args.ProxySession.Request.RequestContentLength > 0)
{
args.ProxyRequest.AllowWriteStreamBuffering = true;
//args.ProxyRequest.AllowWriteStreamBuffering = true;
try
{
var totalbytesRead = 0;
int bytesToRead;
if (args.ProxyRequest.ContentLength < BUFFER_SIZE)
if (args.ProxySession.Request.RequestContentLength < BUFFER_SIZE)
{
bytesToRead = (int)args.ProxyRequest.ContentLength;
bytesToRead = (int)args.ProxySession.Request.RequestContentLength;
}
else
bytesToRead = BUFFER_SIZE;
while (totalbytesRead < (int)args.ProxyRequest.ContentLength)
while (totalbytesRead < (int)args.ProxySession.Request.RequestContentLength)
{
var buffer = args.ClientStreamReader.ReadBytes(bytesToRead);
var buffer = args.Client.ClientStreamReader.ReadBytes(bytesToRead);
totalbytesRead += buffer.Length;
var remainingBytes = (int)args.ProxyRequest.ContentLength - totalbytesRead;
var remainingBytes = (int)args.ProxySession.Request.RequestContentLength - totalbytesRead;
if (remainingBytes < bytesToRead)
{
bytesToRead = remainingBytes;
}
postStream.Write(buffer, 0, buffer.Length);
}
postStream.Close();
}
catch
{
postStream.Close();
postStream.Dispose();
throw;
}
}
//Need to revist, find any potential bugs
else if (args.ProxyRequest.SendChunked)
else if (args.ProxySession.Request.RequestSendChunked)
{
args.ProxyRequest.AllowWriteStreamBuffering = true;
try
{
while (true)
{
var chuchkHead = args.ClientStreamReader.ReadLine();
var chuchkHead = args.Client.ClientStreamReader.ReadLine();
var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);
if (chunkSize != 0)
{
var buffer = args.ClientStreamReader.ReadBytes(chunkSize);
var buffer = args.Client.ClientStreamReader.ReadBytes(chunkSize);
postStream.Write(buffer, 0, buffer.Length);
//chunk trail
args.ClientStreamReader.ReadLine();
args.Client.ClientStreamReader.ReadLine();
}
else
{
args.ClientStreamReader.ReadLine();
args.Client.ClientStreamReader.ReadLine();
break;
}
}
postStream.Close();
}
catch
{
postStream.Close();
postStream.Dispose();
throw;
}
}
......
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy
......@@ -17,73 +20,64 @@ namespace Titanium.Web.Proxy
//Called asynchronously when a request was successfully and we received the response
private static void HandleHttpSessionResponse(SessionEventArgs args)
{
args.ProxySession.ReceiveResponse();
try
{
args.ServerResponse = (HttpWebResponse)args.ProxyRequest.GetResponse();
}
catch (WebException webEx)
{
//Things line 404, 500 etc
args.ServerResponse = webEx.Response as HttpWebResponse;
}
try
{
if (args.ServerResponse != null)
{
args.ResponseHeaders = ReadResponseHeaders(args.ServerResponse);
args.ResponseStream = args.ServerResponse.GetResponseStream();
args.ProxySession.Response.ResponseHeaders = ReadResponseHeaders(args.ProxySession);
args.ProxySession.Response.ResponseStream = args.ProxySession.ProxyClient.ServerStreamReader.BaseStream;
if (BeforeResponse != null)
{
args.ResponseEncoding = args.ServerResponse.GetEncoding();
BeforeResponse(null, args);
}
args.ResponseLocked = true;
if (BeforeResponse != null)
{
args.ProxySession.Response.ResponseEncoding = args.ProxySession.GetResponseEncoding();
BeforeResponse(null, args);
}
if (args.ResponseBodyRead)
{
var isChunked = args.ServerResponse.GetResponseHeader("transfer-encoding").ToLower().Contains("chunked");
var contentEncoding = args.ServerResponse.ContentEncoding;
args.ProxySession.Response.ResponseLocked = true;
switch (contentEncoding.ToLower())
{
case "gzip":
args.ResponseBody = CompressionHelper.CompressGzip(args.ResponseBody);
break;
case "deflate":
args.ResponseBody = CompressionHelper.CompressDeflate(args.ResponseBody);
break;
case "zlib":
args.ResponseBody = CompressionHelper.CompressZlib(args.ResponseBody);
break;
}
if (args.ProxySession.Response.ResponseBodyRead)
{
var isChunked = args.ProxySession.Response.IsChunked;
var contentEncoding = args.ProxySession.Response.ResponseContentEncoding;
WriteResponseStatus(args.ServerResponse.ProtocolVersion, args.ServerResponse.StatusCode,
args.ServerResponse.StatusDescription, args.ClientStreamWriter);
WriteResponseHeaders(args.ClientStreamWriter, args.ResponseHeaders, args.ResponseBody.Length,
isChunked);
WriteResponseBody(args.ClientStream, args.ResponseBody, isChunked);
}
else
if(contentEncoding!=null)
switch (contentEncoding)
{
var isChunked = args.ServerResponse.GetResponseHeader("transfer-encoding").ToLower().Contains("chunked");
WriteResponseStatus(args.ServerResponse.ProtocolVersion, args.ServerResponse.StatusCode,
args.ServerResponse.StatusDescription, args.ClientStreamWriter);
WriteResponseHeaders(args.ClientStreamWriter, args.ResponseHeaders);
WriteResponseBody(args.ResponseStream, args.ClientStream, isChunked);
case "gzip":
args.ProxySession.Response.ResponseBody = CompressionHelper.CompressGzip(args.ProxySession.Response.ResponseBody);
break;
case "deflate":
args.ProxySession.Response.ResponseBody = CompressionHelper.CompressDeflate(args.ProxySession.Response.ResponseBody);
break;
case "zlib":
args.ProxySession.Response.ResponseBody = CompressionHelper.CompressZlib(args.ProxySession.Response.ResponseBody);
break;
}
args.ClientStream.Flush();
WriteResponseStatus(args.ProxySession.Response.ResponseProtocolVersion, args.ProxySession.Response.ResponseStatusCode,
args.ProxySession.Response.ResponseStatusDescription, args.Client.ClientStreamWriter);
WriteResponseHeaders(args.Client.ClientStreamWriter, args.ProxySession.Response.ResponseHeaders, args.ProxySession.Response.ResponseBody.Length,
isChunked);
WriteResponseBody(args.Client.ClientStream, args.ProxySession.Response.ResponseBody, isChunked);
}
else
{
WriteResponseStatus(args.ProxySession.Response.ResponseProtocolVersion, args.ProxySession.Response.ResponseStatusCode,
args.ProxySession.Response.ResponseStatusDescription, args.Client.ClientStreamWriter);
WriteResponseHeaders(args.Client.ClientStreamWriter, args.ProxySession.Response.ResponseHeaders);
if (args.ProxySession.Response.IsChunked || args.ProxySession.Response.ContentLength > 0)
WriteResponseBody(args.ProxySession.ProxyClient.ServerStreamReader, args.Client.ClientStream, args.ProxySession.Response.IsChunked, args.ProxySession.Response.ContentLength);
}
args.Client.ClientStream.Flush();
}
catch
{
Dispose(args.Client, args.ClientStream, args.ClientStreamReader, args.ClientStreamWriter, args);
Dispose(args.Client.TcpClient, args.Client.ClientStream, args.Client.ClientStreamReader, args.Client.ClientStreamWriter, args);
}
finally
{
......@@ -91,39 +85,53 @@ namespace Titanium.Web.Proxy
}
}
private static List<HttpHeader> ReadResponseHeaders(HttpWebResponse response)
private static List<HttpHeader> ReadResponseHeaders(HttpWebSession response)
{
var returnHeaders = new List<HttpHeader>();
string cookieHeaderName = null;
string cookieHeaderValue = null;
foreach (string headerKey in response.Headers.Keys)
for (var i = 0; i < response.Response.ResponseHeaders.Count; i++)
{
if (headerKey.ToLower() == "set-cookie")
switch (response.Response.ResponseHeaders[i].Name.ToLower())
{
cookieHeaderName = headerKey;
cookieHeaderValue = response.Headers[headerKey];
}
else
returnHeaders.Add(new HttpHeader(headerKey, response.Headers[headerKey]));
}
case "content-length":
response.Response.ContentLength = int.Parse(response.Response.ResponseHeaders[i].Value.Trim());
break;
if (!string.IsNullOrWhiteSpace(cookieHeaderValue))
{
response.Headers.Remove(cookieHeaderName);
var cookies = CookieSplitRegEx.Split(cookieHeaderValue);
foreach (var cookie in cookies)
returnHeaders.Add(new HttpHeader("Set-Cookie", cookie));
case "content-encoding":
response.Response.ResponseContentEncoding = response.Response.ResponseHeaders[i].Value.Trim().ToLower();
break;
case "content-type":
if (response.Response.ResponseHeaders[i].Value.Contains(";"))
{
response.Response.ResponseContentType = response.Response.ResponseHeaders[i].Value.Split(';')[0].Trim();
response.Response.ResponseCharacterSet = response.Response.ResponseHeaders[i].Value.Split(';')[1].ToLower().Replace("charset=", string.Empty).Trim();
}
else
response.Response.ResponseContentType = response.Response.ResponseHeaders[i].Value.ToLower().Trim();
break;
case "transfer-encoding":
if (response.Response.ResponseHeaders[i].Value.ToLower().Contains("chunked"))
response.Response.IsChunked = true;
break;
case "connection":
if (response.Response.ResponseHeaders[i].Value.ToLower().Contains("close"))
response.Response.ResponseKeepAlive = false;
break;
default:
break;
}
}
return returnHeaders;
return response.Response.ResponseHeaders;
}
private static void WriteResponseStatus(Version version, HttpStatusCode code, string description,
private static void WriteResponseStatus(Version version, string code, string description,
StreamWriter responseWriter)
{
var s = string.Format("HTTP/{0}.{1} {2} {3}", version.Major, version.Minor, (int)code, description);
var s = string.Format("HTTP/{0}.{1} {2} {3}", version.Major, version.Minor, code, description);
responseWriter.WriteLine(s);
}
......@@ -131,7 +139,7 @@ namespace Titanium.Web.Proxy
{
if (headers != null)
{
FixProxyHeaders(headers);
FixResponseProxyHeaders(headers);
foreach (var header in headers)
{
......@@ -142,23 +150,29 @@ namespace Titanium.Web.Proxy
responseWriter.WriteLine();
responseWriter.Flush();
}
private static void FixProxyHeaders(List<HttpHeader> headers)
private static void FixResponseProxyHeaders(List<HttpHeader> headers)
{
//If proxy-connection close was returned inform to close the connection
if (headers.Any(x => x.Name.ToLower() == "proxy-connection" && x.Value.ToLower() == "close"))
if (headers.Any(x => x.Name.ToLower() == "connection") == false)
var proxyHeader = headers.FirstOrDefault(x => x.Name.ToLower() == "proxy-connection");
var connectionHeader = headers.FirstOrDefault(x => x.Name.ToLower() == "connection");
if (proxyHeader != null)
if (connectionHeader == null)
{
headers.Add(new HttpHeader("connection", "close"));
headers.RemoveAll(x => x.Name.ToLower() == "proxy-connection");
headers.Add(new HttpHeader("connection", proxyHeader.Value));
}
else
headers.Find(x => x.Name.ToLower() == "connection").Value = "close";
{
connectionHeader.Value = "close";
}
headers.RemoveAll(x => x.Name.ToLower() == "proxy-connection");
}
private static void WriteResponseHeaders(StreamWriter responseWriter, List<HttpHeader> headers, int length,
bool isChunked)
{
FixProxyHeaders(headers);
FixResponseProxyHeaders(headers);
if (!isChunked)
{
......@@ -193,39 +207,68 @@ namespace Titanium.Web.Proxy
WriteResponseBodyChunked(data, clientStream);
}
private static void WriteResponseBody(Stream inStream, Stream outStream, bool isChunked)
private static void WriteResponseBody(CustomBinaryReader inStreamReader, Stream outStream, bool isChunked, int BodyLength)
{
if (!isChunked)
{
int bytesToRead = BUFFER_SIZE;
if (BodyLength < BUFFER_SIZE)
bytesToRead = BodyLength;
var buffer = new byte[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = inStream.Read(buffer, 0, buffer.Length)) > 0)
var bytesRead = 0;
var totalBytesRead = 0;
while ((bytesRead += inStreamReader.BaseStream.Read(buffer, 0, bytesToRead)) > 0)
{
outStream.Write(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
if (totalBytesRead == BodyLength)
break;
bytesRead = 0;
var remainingBytes = (BodyLength - totalBytesRead);
bytesToRead = remainingBytes > BUFFER_SIZE ? BUFFER_SIZE : remainingBytes;
}
}
else
WriteResponseBodyChunked(inStream, outStream);
WriteResponseBodyChunked(inStreamReader, outStream);
}
//Send chunked response
private static void WriteResponseBodyChunked(Stream inStream, Stream outStream)
private static void WriteResponseBodyChunked(CustomBinaryReader inStreamReader, Stream outStream)
{
var buffer = new byte[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = inStream.Read(buffer, 0, buffer.Length)) > 0)
while (true)
{
var chunkHead = Encoding.ASCII.GetBytes(bytesRead.ToString("x2"));
var chuchkHead = inStreamReader.ReadLine();
var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);
if (chunkSize != 0)
{
var buffer = inStreamReader.ReadBytes(chunkSize);
var chunkHead = Encoding.ASCII.GetBytes(chunkSize.ToString("x2"));
outStream.Write(chunkHead, 0, chunkHead.Length);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length);
outStream.Write(buffer, 0, chunkSize);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length);
outStream.Write(chunkHead, 0, chunkHead.Length);
outStream.Write(ChunkTrail, 0, ChunkTrail.Length);
outStream.Write(buffer, 0, bytesRead);
outStream.Write(ChunkTrail, 0, ChunkTrail.Length);
inStreamReader.ReadLine();
}
else
{
inStreamReader.ReadLine();
outStream.Write(ChunkEnd, 0, ChunkEnd.Length);
break;
}
}
outStream.Write(ChunkEnd, 0, ChunkEnd.Length);
}
private static void WriteResponseBodyChunked(byte[] data, Stream outStream)
......@@ -233,9 +276,9 @@ namespace Titanium.Web.Proxy
var chunkHead = Encoding.ASCII.GetBytes(data.Length.ToString("x2"));
outStream.Write(chunkHead, 0, chunkHead.Length);
outStream.Write(ChunkTrail, 0, ChunkTrail.Length);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length);
outStream.Write(data, 0, data.Length);
outStream.Write(ChunkTrail, 0, ChunkTrail.Length);
outStream.Write(NewLineBytes, 0, NewLineBytes.Length);
outStream.Write(ChunkEnd, 0, ChunkEnd.Length);
}
......
......@@ -50,9 +50,28 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\DotNetZip.1.9.7\lib\net20\Ionic.Zip.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Threading.Tasks">
<HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Threading.Tasks.Extensions">
<HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Threading.Tasks.Extensions.Desktop">
<HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.configuration" />
<Reference Include="System.Core" />
<Reference Include="System.IO">
<HintPath>..\packages\Microsoft.Bcl.1.1.10\lib\net40\System.IO.dll</HintPath>
</Reference>
<Reference Include="System.Net" />
<Reference Include="System.Runtime">
<HintPath>..\packages\Microsoft.Bcl.1.1.10\lib\net40\System.Runtime.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks">
<HintPath>..\packages\Microsoft.Bcl.1.1.10\lib\net40\System.Threading.Tasks.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
......@@ -66,12 +85,14 @@
<Compile Include="Helpers\CertificateManager.cs" />
<Compile Include="Helpers\Firefox.cs" />
<Compile Include="Helpers\SystemProxy.cs" />
<Compile Include="Network\TcpExtensions.cs" />
<Compile Include="Network\TcpConnectionManager.cs" />
<Compile Include="Models\HttpHeader.cs" />
<Compile Include="Network\HttpWebClient.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RequestHandler.cs" />
<Compile Include="ResponseHandler.cs" />
<Compile Include="Helpers\CustomBinaryReader.cs" />
<Compile Include="Helpers\NetFramework.cs" />
<Compile Include="Helpers\Compression.cs" />
<Compile Include="ProxyServer.cs" />
<Compile Include="EventArguments\SessionEventArgs.cs" />
......@@ -80,6 +101,7 @@
</ItemGroup>
<ItemGroup />
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
<None Include="Titanium_Proxy_Test_Root.cer">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
......@@ -98,6 +120,11 @@
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
<Import Project="..\packages\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets')" />
<Target Name="EnsureBclBuildImported" BeforeTargets="BeforeBuild" Condition="'$(BclBuildImported)' == ''">
<Error Condition="!Exists('..\packages\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets')" Text="This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=317567." HelpKeyword="BCLBUILD2001" />
<Error Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets')" Text="The build restored NuGet packages. Build the project again to include these packages in the build. For more information, see http://go.microsoft.com/fwlink/?LinkID=317568." HelpKeyword="BCLBUILD2002" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
......
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.6.10.0" newVersion="2.6.10.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.6.10.0" newVersion="2.6.10.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
\ No newline at end of file
......@@ -2,4 +2,7 @@
<packages>
<package id="DotNetZip" version="1.9.7" targetFramework="net40" />
<package id="DotNetZip" version="1.9.7" targetFramework="net45" />
<package id="Microsoft.Bcl" version="1.1.10" targetFramework="net40" />
<package id="Microsoft.Bcl.Async" version="1.0.168" targetFramework="net40" />
<package id="Microsoft.Bcl.Build" version="1.0.14" targetFramework="net40" />
</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