Commit 6cb2ed4f authored by titanium007's avatar titanium007

Add inline comments

parent 0381ef56
...@@ -8,6 +8,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -8,6 +8,7 @@ namespace Titanium.Web.Proxy.Helpers
{ {
public class NetFrameworkHelper public class NetFrameworkHelper
{ {
//http://stackoverflow.com/questions/856885/httpwebrequest-to-url-with-dot-at-the-end
public static void URLPeriodFix() public static void URLPeriodFix()
{ {
MethodInfo getSyntax = typeof(UriParser).GetMethod("GetSyntax", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); MethodInfo getSyntax = typeof(UriParser).GetMethod("GetSyntax", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
......
...@@ -74,6 +74,7 @@ namespace Titanium.Web.Proxy ...@@ -74,6 +74,7 @@ namespace Titanium.Web.Proxy
ServicePointManager.DnsRefreshTimeout = 3 * 60 * 1000;//3 minutes ServicePointManager.DnsRefreshTimeout = 3 * 60 * 1000;//3 minutes
ServicePointManager.MaxServicePointIdleTime = 3 * 60 * 1000; ServicePointManager.MaxServicePointIdleTime = 3 * 60 * 1000;
//HttpWebRequest certificate validation callback
ServicePointManager.ServerCertificateValidationCallback = delegate(object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) ServicePointManager.ServerCertificateValidationCallback = delegate(object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{ {
if (sslPolicyErrors == SslPolicyErrors.None) return true; if (sslPolicyErrors == SslPolicyErrors.None) return true;
...@@ -81,6 +82,7 @@ namespace Titanium.Web.Proxy ...@@ -81,6 +82,7 @@ namespace Titanium.Web.Proxy
return false; return false;
}; };
//Fix a bug in .NET 4.0
NetFrameworkHelper.URLPeriodFix(); NetFrameworkHelper.URLPeriodFix();
} }
...@@ -107,6 +109,7 @@ namespace Titanium.Web.Proxy ...@@ -107,6 +109,7 @@ namespace Titanium.Web.Proxy
RootCertificateName = RootCertificateName == null ? "Titanium_Proxy_Test_Root" : RootCertificateName; RootCertificateName = RootCertificateName == null ? "Titanium_Proxy_Test_Root" : RootCertificateName;
bool certTrusted = CertManager.CreateTrustedRootCertificate(); bool certTrusted = CertManager.CreateTrustedRootCertificate();
//If certificate was trusted by the machine
if (certTrusted) if (certTrusted)
{ {
SystemProxyHelper.EnableProxyHTTPS("localhost", ListeningPort); SystemProxyHelper.EnableProxyHTTPS("localhost", ListeningPort);
...@@ -143,18 +146,14 @@ namespace Titanium.Web.Proxy ...@@ -143,18 +146,14 @@ namespace Titanium.Web.Proxy
{ {
while (ShouldListen) while (ShouldListen)
{ {
var client = listener.AcceptTcpClient(); var client = listener.AcceptTcpClient();
Task.Factory.StartNew(() => HandleClient(client)); Task.Factory.StartNew(() => HandleClient(client));
} }
} }
catch (ThreadInterruptedException) { } catch (ThreadInterruptedException) { }
catch (SocketException) catch (SocketException) { }
{
}
} }
......
...@@ -26,16 +26,16 @@ namespace Titanium.Web.Proxy ...@@ -26,16 +26,16 @@ namespace Titanium.Web.Proxy
{ {
string connectionGroup = null; string connectionGroup = null;
Stream clientStream = null; Stream clientStream = null;
CustomBinaryReader clientStreamReader = null; CustomBinaryReader clientStreamReader = null;
StreamWriter connectStreamWriter = null; StreamWriter connectStreamWriter = null;
string tunnelHostName = null; string tunnelHostName = null;
int tunnelPort = 0; int tunnelPort = 0;
try try
{ {
connectionGroup = Dns.GetHostEntry(((IPEndPoint)Client.Client.RemoteEndPoint).Address).HostName; //Separate HttpWebRequest connection group for each client
connectionGroup = ((IPEndPoint)Client.Client.RemoteEndPoint).Address.ToString();
clientStream = Client.GetStream(); clientStream = Client.GetStream();
...@@ -55,7 +55,7 @@ namespace Titanium.Web.Proxy ...@@ -55,7 +55,7 @@ namespace Titanium.Web.Proxy
{ {
throw new EndOfStreamException(); throw new EndOfStreamException();
} }
//break up the line into three components //break up the line into three components (method, remote URL & Http Version)
String[] splitBuffer = httpCmd.Split(spaceSplit, 3); String[] splitBuffer = httpCmd.Split(spaceSplit, 3);
String method = splitBuffer[0]; String method = splitBuffer[0];
...@@ -73,12 +73,13 @@ namespace Titanium.Web.Proxy ...@@ -73,12 +73,13 @@ namespace Titanium.Web.Proxy
RequestVersion = "HTTP/1.0"; RequestVersion = "HTTP/1.0";
} }
//Client wants to create a secure tcp tunnel (its a HTTPS request)
if (splitBuffer[0].ToUpper() == "CONNECT") if (splitBuffer[0].ToUpper() == "CONNECT")
{ {
//Browser wants to create a secure tunnel //Browser wants to create a secure tunnel
//instead = we are going to perform a man in the middle "attack" //instead = we are going to perform a man in the middle "attack"
//the user's browser should warn them of the certification errors, //the user's browser should warn them of the certification errors,
//so we need to install our root certficate in users machine as Certificate Authority. //to avoid that we need to install our root certficate in users machine as Certificate Authority.
remoteUri = "https://" + splitBuffer[1]; remoteUri = "https://" + splitBuffer[1];
tunnelHostName = splitBuffer[1].Split(':')[0]; tunnelHostName = splitBuffer[1].Split(':')[0];
int.TryParse(splitBuffer[1].Split(':')[1], out tunnelPort); int.TryParse(splitBuffer[1].Split(':')[1], out tunnelPort);
...@@ -109,11 +110,9 @@ namespace Titanium.Web.Proxy ...@@ -109,11 +110,9 @@ namespace Titanium.Web.Proxy
connectStreamWriter.Flush(); connectStreamWriter.Flush();
//If port is not 443 its not a HTTP request, so just relay
if (tunnelPort != 443) if (tunnelPort != 443)
{ {
TcpHelper.SendRaw(tunnelHostName, tunnelPort, clientStreamReader.BaseStream); TcpHelper.SendRaw(tunnelHostName, tunnelPort, clientStreamReader.BaseStream);
if (clientStream != null) if (clientStream != null)
...@@ -121,22 +120,29 @@ namespace Titanium.Web.Proxy ...@@ -121,22 +120,29 @@ namespace Titanium.Web.Proxy
return; return;
} }
//Create the fake certificate signed using our fake certificate authority
Monitor.Enter(certificateAccessLock); Monitor.Enter(certificateAccessLock);
var _certificate = ProxyServer.CertManager.CreateCertificate(tunnelHostName); var _certificate = ProxyServer.CertManager.CreateCertificate(tunnelHostName);
Monitor.Exit(certificateAccessLock); Monitor.Exit(certificateAccessLock);
SslStream sslStream = null; SslStream sslStream = null;
//Pinned certificate clients cannot be proxied
//Example dropbox.com uses certificate pinning
//So just relay the request after identifying it by first failure
if (!pinnedCertificateClients.Contains(tunnelHostName) && isSecure) if (!pinnedCertificateClients.Contains(tunnelHostName) && isSecure)
{ {
sslStream = new SslStream(clientStream, true); sslStream = new SslStream(clientStream, true);
try try
{ {
//Successfully managed to authenticate the client using the fake certificate
sslStream.AuthenticateAsServer(_certificate, false, SslProtocols.Tls | SslProtocols.Ssl3 | SslProtocols.Ssl2, false); sslStream.AuthenticateAsServer(_certificate, false, SslProtocols.Tls | SslProtocols.Ssl3 | SslProtocols.Ssl2, false);
} }
catch (AuthenticationException ex) catch (AuthenticationException ex)
{ {
//if authentication failed it could be because client uses pinned certificates
//So add the hostname to this list so that next time we can relay without touching it (tunnel the request)
if (pinnedCertificateClients.Contains(tunnelHostName) == false) if (pinnedCertificateClients.Contains(tunnelHostName) == false)
{ {
pinnedCertificateClients.Add(tunnelHostName); pinnedCertificateClients.Add(tunnelHostName);
...@@ -147,8 +153,7 @@ namespace Titanium.Web.Proxy ...@@ -147,8 +153,7 @@ namespace Titanium.Web.Proxy
} }
else else
{ {
//Hostname was a previously failed request due to certificate pinning, just relay (tunnel the request)
TcpHelper.SendRaw(tunnelHostName, tunnelPort, clientStreamReader.BaseStream); TcpHelper.SendRaw(tunnelHostName, tunnelPort, clientStreamReader.BaseStream);
if (clientStream != null) if (clientStream != null)
...@@ -175,6 +180,7 @@ namespace Titanium.Web.Proxy ...@@ -175,6 +180,7 @@ namespace Titanium.Web.Proxy
securehost = remoteUri; securehost = remoteUri;
} }
//Now create the request
HandleHttpSessionRequest(Client, httpCmd, connectionGroup, clientStream, tunnelHostName, requestLines, clientStreamReader, securehost); HandleHttpSessionRequest(Client, httpCmd, connectionGroup, clientStream, tunnelHostName, requestLines, clientStreamReader, securehost);
...@@ -203,10 +209,8 @@ namespace Titanium.Web.Proxy ...@@ -203,10 +209,8 @@ namespace Titanium.Web.Proxy
finally finally
{ {
} }
} }
private static void HandleHttpSessionRequest(TcpClient Client, string httpCmd, string connectionGroup, Stream clientStream, string tunnelHostName, List<string> requestLines, CustomBinaryReader clientStreamReader, string securehost) private static void HandleHttpSessionRequest(TcpClient Client, string httpCmd, string connectionGroup, Stream clientStream, string tunnelHostName, List<string> requestLines, CustomBinaryReader clientStreamReader, string securehost)
{ {
...@@ -221,6 +225,7 @@ namespace Titanium.Web.Proxy ...@@ -221,6 +225,7 @@ namespace Titanium.Web.Proxy
args.securehost = securehost; args.securehost = securehost;
try try
{ {
//break up the line into three components (method, remote URL & Http Version)
var splitBuffer = httpCmd.Split(spaceSplit, 3); var splitBuffer = httpCmd.Split(spaceSplit, 3);
if (splitBuffer.Length != 3) if (splitBuffer.Length != 3)
...@@ -264,10 +269,10 @@ namespace Titanium.Web.Proxy ...@@ -264,10 +269,10 @@ namespace Titanium.Web.Proxy
var rawHeader = requestLines[i]; var rawHeader = requestLines[i];
String[] header = rawHeader.ToLower().Trim().Split(colonSpaceSplit, 2, StringSplitOptions.None); String[] header = rawHeader.ToLower().Trim().Split(colonSpaceSplit, 2, StringSplitOptions.None);
//if request was upgrade to web-socket protocol then relay the request without proxying
if ((header[0] == "upgrade") && (header[1] == "websocket")) if ((header[0] == "upgrade") && (header[1] == "websocket"))
{ {
TcpHelper.SendRaw(httpCmd, tunnelHostName, ref requestLines, args.IsSSLRequest, clientStreamReader.BaseStream); TcpHelper.SendRaw(httpCmd, tunnelHostName, ref requestLines, args.IsSSLRequest, clientStreamReader.BaseStream);
if (clientStream != null) if (clientStream != null)
...@@ -285,6 +290,7 @@ namespace Titanium.Web.Proxy ...@@ -285,6 +290,7 @@ namespace Titanium.Web.Proxy
args.ProxyRequest.AllowAutoRedirect = false; args.ProxyRequest.AllowAutoRedirect = false;
args.ProxyRequest.AutomaticDecompression = DecompressionMethods.None; args.ProxyRequest.AutomaticDecompression = DecompressionMethods.None;
//If requested interception
if (BeforeRequest != null) if (BeforeRequest != null)
{ {
args.RequestHostname = args.ProxyRequest.RequestUri.Host; args.RequestHostname = args.ProxyRequest.RequestUri.Host;
...@@ -299,6 +305,7 @@ namespace Titanium.Web.Proxy ...@@ -299,6 +305,7 @@ namespace Titanium.Web.Proxy
BeforeRequest(null, args); BeforeRequest(null, args);
} }
string tmpLine; string tmpLine;
if (args.CancelRequest) if (args.CancelRequest)
{ {
...@@ -320,7 +327,7 @@ namespace Titanium.Web.Proxy ...@@ -320,7 +327,7 @@ namespace Titanium.Web.Proxy
args.ProxyRequest.ConnectionGroupName = connectionGroup; args.ProxyRequest.ConnectionGroupName = connectionGroup;
args.ProxyRequest.AllowWriteStreamBuffering = true; args.ProxyRequest.AllowWriteStreamBuffering = true;
//If request was modified by user
if (args.RequestWasModified) if (args.RequestWasModified)
{ {
ASCIIEncoding encoding = new ASCIIEncoding(); ASCIIEncoding encoding = new ASCIIEncoding();
...@@ -333,14 +340,18 @@ namespace Titanium.Web.Proxy ...@@ -333,14 +340,18 @@ namespace Titanium.Web.Proxy
} }
else else
{ {
//If its a post/put request, then read the client html body and send it to server
if (method.ToUpper() == "POST" || method.ToUpper() == "PUT") if (method.ToUpper() == "POST" || method.ToUpper() == "PUT")
{ {
args.ProxyRequest.BeginGetRequestStream(new AsyncCallback(GetRequestStreamCallback), args); args.ProxyRequest.BeginGetRequestStream(new AsyncCallback(GetRequestStreamCallback), args);
} }
else else
{ {
//otherwise wait for response asynchronously
args.ProxyRequest.BeginGetResponse(new AsyncCallback(HandleHttpSessionResponse), args); args.ProxyRequest.BeginGetResponse(new AsyncCallback(HandleHttpSessionResponse), args);
//Now read the next request (if keep-Alive is enabled, otherwise exit thus thread)
//If client is pipeling the request, this will be immediately hit before response for previous request was made
if (args.ProxyRequest.KeepAlive) if (args.ProxyRequest.KeepAlive)
{ {
requestLines = new List<string>(); requestLines = new List<string>();
...@@ -357,11 +368,6 @@ namespace Titanium.Web.Proxy ...@@ -357,11 +368,6 @@ namespace Titanium.Web.Proxy
} }
} }
catch (IOException) catch (IOException)
{ {
...@@ -378,10 +384,6 @@ namespace Titanium.Web.Proxy ...@@ -378,10 +384,6 @@ namespace Titanium.Web.Proxy
finally finally
{ {
} }
...@@ -453,6 +455,8 @@ namespace Titanium.Web.Proxy ...@@ -453,6 +455,8 @@ namespace Titanium.Web.Proxy
case "user-agent": case "user-agent":
WebRequest.UserAgent = header[1]; WebRequest.UserAgent = header[1];
break; 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": case "transfer-encoding":
if (header[1].ToLower() == "chunked") if (header[1].ToLower() == "chunked")
WebRequest.SendChunked = true; WebRequest.SendChunked = true;
...@@ -468,7 +472,6 @@ namespace Titanium.Web.Proxy ...@@ -468,7 +472,6 @@ namespace Titanium.Web.Proxy
if (header.Length >= 2) if (header.Length >= 2)
WebRequest.Headers.Add(header[0], header[1]); WebRequest.Headers.Add(header[0], header[1]);
break; break;
} }
...@@ -519,20 +522,16 @@ namespace Titanium.Web.Proxy ...@@ -519,20 +522,16 @@ namespace Titanium.Web.Proxy
postStream.Close(); postStream.Close();
} }
catch (IOException ex) catch (IOException)
{ {
args.ProxyRequest.KeepAlive = false; args.ProxyRequest.KeepAlive = false;
Debug.WriteLine(ex.Message);
return; return;
} }
catch (WebException ex) catch (WebException)
{ {
args.ProxyRequest.KeepAlive = false; args.ProxyRequest.KeepAlive = false;
Debug.WriteLine(ex.Message);
return; return;
} }
...@@ -616,9 +615,11 @@ namespace Titanium.Web.Proxy ...@@ -616,9 +615,11 @@ namespace Titanium.Web.Proxy
} }
} }
//Http request body sent, now wait asynchronously for response
args.ProxyRequest.BeginGetResponse(new AsyncCallback(HandleHttpSessionResponse), args); args.ProxyRequest.BeginGetResponse(new AsyncCallback(HandleHttpSessionResponse), args);
//Now read the next request (if keep-Alive is enabled, otherwise exit thus thread)
//If client is pipeling the request, this will be immediately hit before response for previous request was made
if (args.ProxyRequest.KeepAlive) if (args.ProxyRequest.KeepAlive)
{ {
string httpCmd, tmpLine; string httpCmd, tmpLine;
...@@ -630,6 +631,8 @@ namespace Titanium.Web.Proxy ...@@ -630,6 +631,8 @@ namespace Titanium.Web.Proxy
} }
httpCmd = requestLines.Count() > 0 ? requestLines[0] : null; httpCmd = requestLines.Count() > 0 ? requestLines[0] : null;
TcpClient Client = args.Client; TcpClient Client = args.Client;
//Http request body sent, now wait for next request
HandleHttpSessionRequest(Client, httpCmd, args.ProxyRequest.ConnectionGroupName, args.ClientStream, args.tunnelHostName, requestLines, args.ClientStreamReader, args.securehost); HandleHttpSessionRequest(Client, httpCmd, args.ProxyRequest.ConnectionGroupName, args.ClientStream, args.tunnelHostName, requestLines, args.ClientStreamReader, args.securehost);
} }
} }
......
...@@ -17,6 +17,7 @@ namespace Titanium.Web.Proxy ...@@ -17,6 +17,7 @@ namespace Titanium.Web.Proxy
{ {
partial class ProxyServer partial class ProxyServer
{ {
//Called asynchronously when a request was successfully and we received the response
private static void HandleHttpSessionResponse(IAsyncResult AsynchronousResult) private static void HandleHttpSessionResponse(IAsyncResult AsynchronousResult)
{ {
...@@ -27,7 +28,8 @@ namespace Titanium.Web.Proxy ...@@ -27,7 +28,8 @@ namespace Titanium.Web.Proxy
} }
catch (WebException webEx) catch (WebException webEx)
{ {
args.ProxyRequest.KeepAlive = false; //Things line 404, 500 etc
//args.ProxyRequest.KeepAlive = false;
args.ServerResponse = webEx.Response as HttpWebResponse; args.ServerResponse = webEx.Response as HttpWebResponse;
} }
...@@ -109,35 +111,25 @@ namespace Titanium.Web.Proxy ...@@ -109,35 +111,25 @@ namespace Titanium.Web.Proxy
} }
catch (IOException ex) catch (IOException)
{ {
args.ProxyRequest.KeepAlive = false; args.ProxyRequest.KeepAlive = false;
Debug.WriteLine(ex.Message);
} }
catch (SocketException ex) catch (SocketException ex)
{ {
args.ProxyRequest.KeepAlive = false; args.ProxyRequest.KeepAlive = false;
Debug.WriteLine(ex.Message);
} }
catch (ArgumentException ex) catch (ArgumentException)
{ {
args.ProxyRequest.KeepAlive = false; args.ProxyRequest.KeepAlive = false;
Debug.WriteLine(ex.Message);
} }
catch (WebException ex) catch (WebException)
{ {
args.ProxyRequest.KeepAlive = false; args.ProxyRequest.KeepAlive = false;
Debug.WriteLine(ex.Message);
} }
finally finally
{ {
//Close this HttpWebRequest session
if (args.ProxyRequest != null) args.ProxyRequest.Abort(); if (args.ProxyRequest != null) args.ProxyRequest.Abort();
if (args.ServerResponseStream != null) args.ServerResponseStream.Close(); if (args.ServerResponseStream != null) args.ServerResponseStream.Close();
...@@ -146,6 +138,7 @@ namespace Titanium.Web.Proxy ...@@ -146,6 +138,7 @@ namespace Titanium.Web.Proxy
} }
//If this is false then terminate the tcp client
if (args.ProxyRequest.KeepAlive == false) if (args.ProxyRequest.KeepAlive == false)
{ {
if (responseWriter != null) if (responseWriter != null)
...@@ -245,6 +238,7 @@ namespace Titanium.Web.Proxy ...@@ -245,6 +238,7 @@ namespace Titanium.Web.Proxy
} }
} }
//Send chunked response
public static void SendChunked(Stream InStream, Stream OutStream) public static void SendChunked(Stream InStream, Stream OutStream)
{ {
...@@ -274,8 +268,6 @@ namespace Titanium.Web.Proxy ...@@ -274,8 +268,6 @@ namespace Titanium.Web.Proxy
var ChunkTrail = Encoding.ASCII.GetBytes(Environment.NewLine); var ChunkTrail = Encoding.ASCII.GetBytes(Environment.NewLine);
var ChunkHead = Encoding.ASCII.GetBytes(Data.Length.ToString("x2")); var ChunkHead = Encoding.ASCII.GetBytes(Data.Length.ToString("x2"));
OutStream.Write(ChunkHead, 0, ChunkHead.Length); OutStream.Write(ChunkHead, 0, ChunkHead.Length);
OutStream.Write(ChunkTrail, 0, ChunkTrail.Length); OutStream.Write(ChunkTrail, 0, ChunkTrail.Length);
......
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