internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, Stream stream)
        {
            while (true)
            {
                var chuchkHead = await clientStreamReader.ReadLineAsync().ConfigureAwait(false);

                var chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);

                if (chunkSize != 0)
                {
                    var buffer = await clientStreamReader.ReadBytesAsync(chunkSize);

                    await stream.WriteAsync(buffer, 0, buffer.Length);

                    //chunk trail
                    await clientStreamReader.ReadLineAsync().ConfigureAwait(false);
                }
                else
                {
                    await clientStreamReader.ReadLineAsync().ConfigureAwait(false);

                    break;
                }
            }
        }
        //Send chunked response
        private static async Task WriteResponseBodyChunked(CustomBinaryReader inStreamReader, Stream outStream)
        {
            while (true)
            {
                var chunkHead = await inStreamReader.ReadLineAsync().ConfigureAwait(false);

                var chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);

                if (chunkSize != 0)
                {
                    var buffer = await inStreamReader.ReadBytesAsync(chunkSize).ConfigureAwait(false);

                    var chunkHeadBytes = Encoding.ASCII.GetBytes(chunkSize.ToString("x2"));

                    await outStream.WriteAsync(chunkHeadBytes, 0, chunkHeadBytes.Length).ConfigureAwait(false);

                    await outStream.WriteAsync(Constants.NewLineBytes, 0, Constants.NewLineBytes.Length).ConfigureAwait(false);

                    await outStream.WriteAsync(buffer, 0, chunkSize).ConfigureAwait(false);

                    await outStream.WriteAsync(Constants.NewLineBytes, 0, Constants.NewLineBytes.Length).ConfigureAwait(false);

                    await inStreamReader.ReadLineAsync().ConfigureAwait(false);
                }
                else
                {
                    await inStreamReader.ReadLineAsync().ConfigureAwait(false);

                    await outStream.WriteAsync(Constants.ChunkEnd, 0, Constants.ChunkEnd.Length).ConfigureAwait(false);

                    break;
                }
            }
        }
        /// <summary>
        /// Copies the streams chunked
        /// </summary>
        /// <param name="inStreamReader"></param>
        /// <param name="outStream"></param>
        /// <returns></returns>
        internal static async Task WriteResponseBodyChunked(this CustomBinaryReader inStreamReader, Stream outStream)
        {
            while (true)
            {
                var chunkHead = await inStreamReader.ReadLineAsync();

                var chunkSize = int.Parse(chunkHead, NumberStyles.HexNumber);

                if (chunkSize != 0)
                {
                    var chunkHeadBytes = Encoding.ASCII.GetBytes(chunkSize.ToString("x2"));
                    await outStream.WriteAsync(chunkHeadBytes, 0, chunkHeadBytes.Length);

                    await outStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);

                    await CopyBytesToStream(inStreamReader, outStream, chunkSize);

                    await outStream.WriteAsync(ProxyConstants.NewLineBytes, 0, ProxyConstants.NewLineBytes.Length);

                    await inStreamReader.ReadLineAsync();
                }
                else
                {
                    await inStreamReader.ReadLineAsync();

                    await outStream.WriteAsync(ProxyConstants.ChunkEnd, 0, ProxyConstants.ChunkEnd.Length);

                    break;
                }
            }
        }
Example #4
0
        internal static async Task ReadHeaders(CustomBinaryReader reader, HeaderCollection headerCollection)
        {
            string tmpLine;

            while (!string.IsNullOrEmpty(tmpLine = await reader.ReadLineAsync()))
            {
                var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);
                headerCollection.AddHeader(header[0], header[1]);
            }
        }
Example #5
0
        /// <summary>
        /// Copies the stream chunked
        /// </summary>
        /// <param name="clientStreamReader"></param>
        /// <param name="stream"></param>
        /// <returns></returns>
        internal static async Task CopyBytesToStreamChunked(this CustomBinaryReader clientStreamReader, Stream stream)
        {
            while (true)
            {
                string chuchkHead = await clientStreamReader.ReadLineAsync();

                int chunkSize = int.Parse(chuchkHead, NumberStyles.HexNumber);

                if (chunkSize != 0)
                {
                    await CopyBytesToStream(clientStreamReader, stream, chunkSize);

                    //chunk trail
                    await clientStreamReader.ReadLineAsync();
                }
                else
                {
                    await clientStreamReader.ReadLineAsync();

                    break;
                }
            }
        }
Example #6
0
        /// <summary>
        /// This is called when this proxy acts as a reverse proxy (like a real http server)
        /// So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
        /// </summary>
        /// <param name="endPoint"></param>
        /// <param name="tcpClient"></param>
        /// <returns></returns>
        private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient)
        {
            bool disposed     = false;
            var  clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);

            CustomBinaryReader clientStreamReader = null;
            HttpResponseWriter clientStreamWriter = null;

            try
            {
                if (endPoint.EnableSsl)
                {
                    var clientHelloInfo = await SslTools.PeekClientHello(clientStream);

                    if (clientHelloInfo != null)
                    {
                        var sslStream = new SslStream(clientStream);
                        clientStream = new CustomBufferedStream(sslStream, BufferSize);

                        string sniHostName = clientHelloInfo.GetServerName();

                        string certName    = HttpHelper.GetWildCardDomainName(sniHostName ?? endPoint.GenericCertificateName);
                        var    certificate = CertificateManager.CreateCertificate(certName, false);

                        //Successfully managed to authenticate the client using the fake certificate
                        await sslStream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls, false);
                    }

                    //HTTPS server created - we can now decrypt the client's traffic
                }

                clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
                clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);

                //now read the request line
                string httpCmd = await clientStreamReader.ReadLineAsync();

                //Now create the request
                disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
                                                          endPoint.EnableSsl?endPoint.GenericCertificateName : null, endPoint, null, true);
            }
            finally
            {
                if (!disposed)
                {
                    Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
                }
            }
        }
        /// <summary>
        /// This is called when this proxy acts as a reverse proxy (like a real http server)
        /// So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
        /// </summary>
        /// <param name="endPoint"></param>
        /// <param name="tcpClient"></param>
        /// <returns></returns>
        private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient)
        {
            bool disposed     = false;
            var  clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);

            CustomBinaryReader clientStreamReader = null;
            StreamWriter       clientStreamWriter = null;

            try
            {
                if (endPoint.EnableSsl)
                {
                    var sslStream = new SslStream(clientStream);
                    clientStream = new CustomBufferedStream(sslStream, BufferSize);

                    //implement in future once SNI supported by SSL stream, for now use the same certificate
                    var certificate = CertificateManager.CreateCertificate(endPoint.GenericCertificateName, false);

                    //Successfully managed to authenticate the client using the fake certificate
                    await sslStream.AuthenticateAsServerAsync(certificate, false,
                                                              SslProtocols.Tls, false);

                    //HTTPS server created - we can now decrypt the client's traffic
                }

                clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
                clientStreamWriter = new StreamWriter(clientStream)
                {
                    NewLine = ProxyConstants.NewLine
                };

                //now read the request line
                var httpCmd = await clientStreamReader.ReadLineAsync();

                //Now create the request
                disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
                                                          endPoint.EnableSsl?endPoint.GenericCertificateName : null, endPoint, null);
            }
            finally
            {
                if (!disposed)
                {
                    Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
                }
            }
        }
        internal static async Task ReadHeaders(CustomBinaryReader reader, HeaderCollection headerCollection)
        {
            var nonUniqueResponseHeaders = headerCollection.NonUniqueHeaders;
            var headers = headerCollection.Headers;

            string tmpLine;

            while (!string.IsNullOrEmpty(tmpLine = await reader.ReadLineAsync()))
            {
                var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);

                var newHeader = new HttpHeader(header[0], header[1]);

                //if header exist in non-unique header collection add it there
                if (nonUniqueResponseHeaders.ContainsKey(newHeader.Name))
                {
                    nonUniqueResponseHeaders[newHeader.Name].Add(newHeader);
                }
                //if header is alread in unique header collection then move both to non-unique collection
                else if (headers.ContainsKey(newHeader.Name))
                {
                    var existing = headers[newHeader.Name];

                    var nonUniqueHeaders = new List <HttpHeader>
                    {
                        existing,
                        newHeader
                    };

                    nonUniqueResponseHeaders.Add(newHeader.Name, nonUniqueHeaders);
                    headers.Remove(newHeader.Name);
                }
                //add to unique header collection
                else
                {
                    headers.Add(newHeader.Name, newHeader);
                }
            }
        }
Example #9
0
        //This is called when client is aware of proxy
        //So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
        private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient)
        {
            Stream clientStream = tcpClient.GetStream();

            clientStream.ReadTimeout  = ConnectionTimeOutSeconds * 1000;
            clientStream.WriteTimeout = ConnectionTimeOutSeconds * 1000;

            var clientStreamReader = new CustomBinaryReader(clientStream);
            var clientStreamWriter = new StreamWriter(clientStream)
            {
                NewLine = ProxyConstants.NewLine
            };

            Uri httpRemoteUri;

            try
            {
                //read the first line HTTP command
                var httpCmd = await clientStreamReader.ReadLineAsync();

                if (string.IsNullOrEmpty(httpCmd))
                {
                    Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
                    return;
                }

                //break up the line into three components (method, remote URL & Http Version)
                var httpCmdSplit = httpCmd.Split(ProxyConstants.SpaceSplit, 3);

                //Find the request Verb
                var httpVerb = httpCmdSplit[0];

                if (httpVerb.ToUpper() == "CONNECT")
                {
                    httpRemoteUri = new Uri("http://" + httpCmdSplit[1]);
                }
                else
                {
                    httpRemoteUri = new Uri(httpCmdSplit[1]);
                }

                //parse the HTTP version
                Version version = new Version(1, 1);
                if (httpCmdSplit.Length == 3)
                {
                    string httpVersion = httpCmdSplit[2].Trim();

                    if (httpVersion == "http/1.0")
                    {
                        version = new Version(1, 0);
                    }
                }
                //filter out excluded host names
                var excluded = endPoint.ExcludedHttpsHostNameRegex != null?
                               endPoint.ExcludedHttpsHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x)) : false;


                List <HttpHeader> connectRequestHeaders = null;

                //Client wants to create a secure tcp tunnel (its a HTTPS request)
                if (httpVerb.ToUpper() == "CONNECT" && !excluded && httpRemoteUri.Port != 80)
                {
                    httpRemoteUri = new Uri("https://" + httpCmdSplit[1]);
                    string tmpLine = null;
                    connectRequestHeaders = new List <HttpHeader>();
                    while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync()))
                    {
                        var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);

                        var newHeader = new HttpHeader(header[0], header[1]);
                        connectRequestHeaders.Add(newHeader);
                    }

                    if (await CheckAuthorization(clientStreamWriter, connectRequestHeaders) == false)
                    {
                        Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
                        return;
                    }

                    await WriteConnectResponse(clientStreamWriter, version);

                    SslStream sslStream = null;

                    try
                    {
                        sslStream = new SslStream(clientStream, true);
                        var certificate = certificateCacheManager.CreateCertificate(httpRemoteUri.Host, false);
                        //Successfully managed to authenticate the client using the fake certificate
                        await sslStream.AuthenticateAsServerAsync(certificate, false,
                                                                  SupportedSslProtocols, false);

                        //HTTPS server created - we can now decrypt the client's traffic
                        clientStream = sslStream;

                        clientStreamReader = new CustomBinaryReader(sslStream);
                        clientStreamWriter = new StreamWriter(sslStream)
                        {
                            NewLine = ProxyConstants.NewLine
                        };
                    }
                    catch
                    {
                        if (sslStream != null)
                        {
                            sslStream.Dispose();
                        }

                        Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
                        return;
                    }

                    //Now read the actual HTTPS request line
                    httpCmd = await clientStreamReader.ReadLineAsync();
                }
                //Sorry cannot do a HTTPS request decrypt to port 80 at this time
                else if (httpVerb.ToUpper() == "CONNECT")
                {
                    //Cyphen out CONNECT request headers
                    await clientStreamReader.ReadAllLinesAsync();

                    //write back successfull CONNECT response
                    await WriteConnectResponse(clientStreamWriter, version);

                    await TcpHelper.SendRaw(BUFFER_SIZE, ConnectionTimeOutSeconds, httpRemoteUri.Host, httpRemoteUri.Port,
                                            httpCmd, version, null,
                                            false, SupportedSslProtocols,
                                            new RemoteCertificateValidationCallback(ValidateServerCertificate),
                                            new LocalCertificateSelectionCallback(SelectClientCertificate),
                                            clientStream, tcpConnectionFactory, UpStreamEndPoint);

                    Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
                    return;
                }
                //Now create the request
                await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
                                               httpRemoteUri.Scheme == Uri.UriSchemeHttps?httpRemoteUri.Host : null, endPoint, connectRequestHeaders, null, null);
            }
            catch (Exception)
            {
                Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
            }
        }
        /// <summary>
        ///  Creates a TCP connection to server
        /// </summary>
        /// <param name="server"></param>
        /// <param name="remoteHostName"></param>
        /// <param name="remotePort"></param>
        /// <param name="httpVersion"></param>
        /// <param name="isHttps"></param>
        /// <param name="isConnect"></param>
        /// <param name="upStreamEndPoint"></param>
        /// <param name="externalProxy"></param>
        /// <returns></returns>
        internal async Task <TcpConnection> CreateClient(ProxyServer server,
                                                         string remoteHostName, int remotePort, Version httpVersion, bool isHttps,
                                                         bool isConnect, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy)
        {
            bool useUpstreamProxy = false;

            //check if external proxy is set for HTTP/HTTPS
            if (externalProxy != null && !(externalProxy.HostName == remoteHostName && externalProxy.Port == remotePort))
            {
                useUpstreamProxy = true;

                //check if we need to ByPass
                if (externalProxy.BypassLocalhost && NetworkHelper.IsLocalIpAddress(remoteHostName))
                {
                    useUpstreamProxy = false;
                }
            }

            TcpClient            client = null;
            CustomBufferedStream stream = null;

            try
            {
                client = new TcpClient(upStreamEndPoint);

                //If this proxy uses another external proxy then create a tunnel request for HTTP/HTTPS connections
                if (useUpstreamProxy)
                {
                    await client.ConnectAsync(externalProxy.HostName, externalProxy.Port);
                }
                else
                {
                    await client.ConnectAsync(remoteHostName, remotePort);
                }

                stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);

                if (useUpstreamProxy && (isConnect || isHttps))
                {
                    var writer = new HttpRequestWriter(stream, server.BufferSize);
                    await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");

                    await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");

                    await writer.WriteLineAsync($"{KnownHeaders.Connection}: {KnownHeaders.ConnectionKeepAlive}");

                    if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
                    {
                        await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer);

                        await writer.WriteLineAsync(KnownHeaders.ProxyAuthorization + ": Basic " +
                                                    Convert.ToBase64String(Encoding.UTF8.GetBytes(
                                                                               externalProxy.UserName + ":" + externalProxy.Password)));
                    }

                    await writer.WriteLineAsync();

                    using (var reader = new CustomBinaryReader(stream, server.BufferSize))
                    {
                        string result = await reader.ReadLineAsync();

                        if (!new[] { "200 OK", "connection established" }.Any(s => result.ContainsIgnoreCase(s)))
                        {
                            throw new Exception("Upstream proxy failed to create a secure tunnel");
                        }

                        await reader.ReadAndIgnoreAllLinesAsync();
                    }
                }

                if (isHttps)
                {
                    var sslStream = new SslStream(stream, false, server.ValidateServerCertificate, server.SelectClientCertificate);
                    stream = new CustomBufferedStream(sslStream, server.BufferSize);

                    await sslStream.AuthenticateAsClientAsync(remoteHostName, null, server.SupportedSslProtocols, server.CheckCertificateRevocation);
                }

                client.ReceiveTimeout = server.ConnectionTimeOutSeconds * 1000;
                client.SendTimeout    = server.ConnectionTimeOutSeconds * 1000;
            }
            catch (Exception)
            {
                stream?.Dispose();
                client?.Close();
                throw;
            }

            return(new TcpConnection(server)
            {
                UpStreamProxy = externalProxy,
                UpStreamEndPoint = upStreamEndPoint,
                HostName = remoteHostName,
                Port = remotePort,
                IsHttps = isHttps,
                UseUpstreamProxy = useUpstreamProxy,
                TcpClient = client,
                StreamReader = new CustomBinaryReader(stream, server.BufferSize),
                StreamWriter = new HttpRequestWriter(stream, server.BufferSize),
                Stream = stream,
                Version = httpVersion
            });
        }
        //This is called when client is aware of proxy
        private async void HandleClient(ExplicitProxyEndPoint endPoint, TcpClient client)
        {
            Stream clientStream       = client.GetStream();
            var    clientStreamReader = new CustomBinaryReader(clientStream);
            var    clientStreamWriter = new StreamWriter(clientStream);

            Uri httpRemoteUri;

            try
            {
                //read the first line HTTP command
                var httpCmd = await clientStreamReader.ReadLineAsync();

                if (string.IsNullOrEmpty(httpCmd))
                {
                    Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
                    return;
                }

                //break up the line into three components (method, remote URL & Http Version)
                var httpCmdSplit = httpCmd.Split(ProxyConstants.SpaceSplit, 3);

                //Find the request Verb
                var httpVerb = httpCmdSplit[0];

                if (httpVerb.ToUpper() == "CONNECT")
                {
                    httpRemoteUri = new Uri("http://" + httpCmdSplit[1]);
                }
                else
                {
                    httpRemoteUri = new Uri(httpCmdSplit[1]);
                }

                //parse the HTTP version
                Version version = new Version(1, 1);
                if (httpCmdSplit.Length == 3)
                {
                    string httpVersion = httpCmdSplit[2].Trim();

                    if (httpVersion == "http/1.0")
                    {
                        version = new Version(1, 0);
                    }
                }
                //filter out excluded host names
                var excluded = endPoint.ExcludedHttpsHostNameRegex != null?
                               endPoint.ExcludedHttpsHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x)) : false;

                //Client wants to create a secure tcp tunnel (its a HTTPS request)
                if (httpVerb.ToUpper() == "CONNECT" && !excluded && httpRemoteUri.Port != 80)
                {
                    httpRemoteUri = new Uri("https://" + httpCmdSplit[1]);
                    await clientStreamReader.ReadAllLinesAsync();

                    await WriteConnectResponse(clientStreamWriter, version);

                    SslStream sslStream = null;

                    try
                    {
                        //create the Tcp Connection to server and then release it to connection cache
                        //Just doing what CONNECT request is asking as to do
                        var tunnelClient = await tcpConnectionCacheManager.GetClient(httpRemoteUri.Host, httpRemoteUri.Port, true, version,
                                                                                     UpStreamHttpProxy, UpStreamHttpsProxy, BUFFER_SIZE, SupportedSslProtocols, new RemoteCertificateValidationCallback(ValidateServerCertificate),
                                                                                     new LocalCertificateSelectionCallback(SelectClientCertificate));

                        await tcpConnectionCacheManager.ReleaseClient(tunnelClient);

                        sslStream = new SslStream(clientStream, true);
                        var certificate = await certificateCacheManager.CreateCertificate(httpRemoteUri.Host, false);

                        //Successfully managed to authenticate the client using the fake certificate
                        await sslStream.AuthenticateAsServerAsync(certificate, false,
                                                                  SupportedSslProtocols, false);

                        //HTTPS server created - we can now decrypt the client's traffic
                        clientStream = sslStream;

                        clientStreamReader = new CustomBinaryReader(sslStream);
                        clientStreamWriter = new StreamWriter(sslStream);
                    }
                    catch
                    {
                        if (sslStream != null)
                        {
                            sslStream.Dispose();
                        }

                        Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
                        return;
                    }

                    //Now read the actual HTTPS request line
                    httpCmd = await clientStreamReader.ReadLineAsync();
                }
                //Sorry cannot do a HTTPS request decrypt to port 80 at this time
                else if (httpVerb.ToUpper() == "CONNECT")
                {
                    //Cyphen out CONNECT request headers
                    await clientStreamReader.ReadAllLinesAsync();

                    //write back successfull CONNECT response
                    await WriteConnectResponse(clientStreamWriter, version);

                    //Just relay the request/response without decrypting it
                    await TcpHelper.SendRaw(clientStream, null, null, httpRemoteUri.Host, httpRemoteUri.Port,
                                            false, SupportedSslProtocols);

                    Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
                    return;
                }

                //Now create the request
                await HandleHttpSessionRequest(client, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
                                               httpRemoteUri.Scheme == Uri.UriSchemeHttps?true : false);
            }
            catch
            {
                Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
            }
        }
        /// <summary>
        /// This is the core request handler method for a particular connection from client
        /// </summary>
        /// <param name="client"></param>
        /// <param name="httpCmd"></param>
        /// <param name="clientStream"></param>
        /// <param name="clientStreamReader"></param>
        /// <param name="clientStreamWriter"></param>
        /// <param name="isHttps"></param>
        /// <returns></returns>
        private async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
                                                    CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, bool isHttps)
        {
            TcpConnectionCache connection = null;

            //Loop through each subsequest request on this particular client connection
            //(assuming HTTP connection is kept alive by client)
            while (true)
            {
                if (string.IsNullOrEmpty(httpCmd))
                {
                    Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
                    break;
                }

                var args = new SessionEventArgs(BUFFER_SIZE, HandleHttpSessionResponse);
                args.ProxyClient.TcpClient = client;

                try
                {
                    //break up the line into three components (method, remote URL & Http Version)
                    var httpCmdSplit = httpCmd.Split(ProxyConstants.SpaceSplit, 3);

                    var httpMethod = httpCmdSplit[0];

                    //find the request HTTP version
                    Version version = new Version(1, 1);
                    if (httpCmdSplit.Length == 3)
                    {
                        var httpVersion = httpCmdSplit[2].ToLower().Trim();

                        if (httpVersion == "http/1.0")
                        {
                            version = new Version(1, 0);
                        }
                    }

                    args.WebSession.Request.RequestHeaders = new List <HttpHeader>();

                    //Read the request headers
                    string tmpLine;
                    while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync()))
                    {
                        var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);
                        args.WebSession.Request.RequestHeaders.Add(new HttpHeader(header[0], header[1]));
                    }

                    var httpRemoteUri = new Uri(!isHttps ? httpCmdSplit[1] : (string.Concat("https://", args.WebSession.Request.Host, httpCmdSplit[1])));
#if DEBUG
                    //Just ignore local requests while Debugging
                    //Its annoying
                    if (httpRemoteUri.Host.Contains("localhost"))
                    {
                        Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
                        break;
                    }
#endif
                    args.WebSession.Request.RequestUri = httpRemoteUri;

                    args.WebSession.Request.Method      = httpMethod;
                    args.WebSession.Request.HttpVersion = version;
                    args.ProxyClient.ClientStream       = clientStream;
                    args.ProxyClient.ClientStreamReader = clientStreamReader;
                    args.ProxyClient.ClientStreamWriter = clientStreamWriter;

                    PrepareRequestHeaders(args.WebSession.Request.RequestHeaders, args.WebSession);
                    args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority;

                    //If user requested interception do it
                    if (BeforeRequest != null)
                    {
                        Delegate[] invocationList = BeforeRequest.GetInvocationList();
                        Task[]     handlerTasks   = new Task[invocationList.Length];

                        for (int i = 0; i < invocationList.Length; i++)
                        {
                            handlerTasks[i] = ((Func <object, SessionEventArgs, Task>)invocationList[i])(null, args);
                        }

                        await Task.WhenAll(handlerTasks);
                    }

                    //if upgrading to websocket then relay the requet without reading the contents
                    if (args.WebSession.Request.UpgradeToWebSocket)
                    {
                        await TcpHelper.SendRaw(clientStream, httpCmd, args.WebSession.Request.RequestHeaders,
                                                httpRemoteUri.Host, httpRemoteUri.Port, args.IsHttps, SupportedSslProtocols);

                        Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
                        return;
                    }

                    //construct the web request that we are going to issue on behalf of the client.
                    connection = await tcpConnectionCacheManager.GetClient(args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.IsHttps, version,
                                                                           UpStreamHttpProxy, UpStreamHttpsProxy, BUFFER_SIZE, SupportedSslProtocols, new RemoteCertificateValidationCallback(ValidateServerCertificate),
                                                                           new LocalCertificateSelectionCallback(SelectClientCertificate));

                    args.WebSession.Request.RequestLocked = true;

                    //If request was cancelled by user then dispose the client
                    if (args.WebSession.Request.CancelRequest)
                    {
                        Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
                        break;
                    }

                    //if expect continue is enabled then send the headers first
                    //and see if server would return 100 conitinue
                    if (args.WebSession.Request.ExpectContinue)
                    {
                        args.WebSession.SetConnection(connection);
                        await args.WebSession.SendRequest(Enable100ContinueBehaviour);
                    }

                    //If 100 continue was the response inform that to the client
                    if (Enable100ContinueBehaviour)
                    {
                        if (args.WebSession.Request.Is100Continue)
                        {
                            await WriteResponseStatus(args.WebSession.Response.HttpVersion, "100",
                                                      "Continue", args.ProxyClient.ClientStreamWriter);

                            await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
                        }
                        else if (args.WebSession.Request.ExpectationFailed)
                        {
                            await WriteResponseStatus(args.WebSession.Response.HttpVersion, "417",
                                                      "Expectation Failed", args.ProxyClient.ClientStreamWriter);

                            await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
                        }
                    }

                    //If expect continue is not enabled then set the connectio and send request headers
                    if (!args.WebSession.Request.ExpectContinue)
                    {
                        args.WebSession.SetConnection(connection);
                        await args.WebSession.SendRequest(Enable100ContinueBehaviour);
                    }

                    //If request was modified by user
                    if (args.WebSession.Request.RequestBodyRead)
                    {
                        if (args.WebSession.Request.ContentEncoding != null)
                        {
                            args.WebSession.Request.RequestBody = await GetCompressedResponseBody(args.WebSession.Request.ContentEncoding, args.WebSession.Request.RequestBody);
                        }
                        //chunked send is not supported as of now
                        args.WebSession.Request.ContentLength = args.WebSession.Request.RequestBody.Length;

                        var newStream = args.WebSession.ServerConnection.Stream;
                        await newStream.WriteAsync(args.WebSession.Request.RequestBody, 0, args.WebSession.Request.RequestBody.Length);
                    }
                    else
                    {
                        if (!args.WebSession.Request.ExpectationFailed)
                        {
                            //If its a post/put request, then read the client html body and send it to server
                            if (httpMethod.ToUpper() == "POST" || httpMethod.ToUpper() == "PUT")
                            {
                                await SendClientRequestBody(args);
                            }
                        }
                    }

                    //If not expectation failed response was returned by server then parse response
                    if (!args.WebSession.Request.ExpectationFailed)
                    {
                        await HandleHttpSessionResponse(args);
                    }

                    //if connection is closing exit
                    if (args.WebSession.Response.ResponseKeepAlive == false)
                    {
                        Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
                        return;
                    }

                    //send the tcp connection to server back to connection cache for reuse
                    await tcpConnectionCacheManager.ReleaseClient(connection);

                    // read the next request
                    httpCmd = await clientStreamReader.ReadLineAsync();
                }
                catch
                {
                    Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
                    break;
                }
            }
        }
        /// <summary>
        /// Creates a TCP connection to server
        /// </summary>
        /// <param name="bufferSize"></param>
        /// <param name="connectionTimeOutSeconds"></param>
        /// <param name="remoteHostName"></param>
        /// <param name="httpCmd"></param>
        /// <param name="httpVersion"></param>
        /// <param name="isHttps"></param>
        /// <param name="remotePort"></param>
        /// <param name="supportedSslProtocols"></param>
        /// <param name="remoteCertificateValidationCallback"></param>
        /// <param name="localCertificateSelectionCallback"></param>
        /// <param name="externalHttpProxy"></param>
        /// <param name="externalHttpsProxy"></param>
        /// <param name="clientStream"></param>
        /// <param name="upStreamEndPoint"></param>
        /// <returns></returns>
        internal async Task <TcpConnection> CreateClient(int bufferSize, int connectionTimeOutSeconds,
                                                         string remoteHostName, int remotePort, Version httpVersion,
                                                         bool isHttps, SslProtocols supportedSslProtocols,
                                                         RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
                                                         ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy,
                                                         Stream clientStream, EndPoint upStreamEndPoint)
        {
            TcpClient client;
            Stream    stream;

            if (isHttps)
            {
                SslStream sslStream = null;

                //If this proxy uses another external proxy then create a tunnel request for HTTPS connections
                if (externalHttpsProxy != null && externalHttpsProxy.HostName != remoteHostName)
                {
                    client = new TcpClient();
                    client.Client.Bind(upStreamEndPoint);
                    await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port);

                    stream = client.GetStream();

                    using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true)
                    {
                        NewLine = ProxyConstants.NewLine
                    })
                    {
                        await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");

                        await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");

                        await writer.WriteLineAsync("Connection: Keep-Alive");

                        if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null)
                        {
                            await writer.WriteLineAsync("Proxy-Connection: keep-alive");

                            await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password)));
                        }
                        await writer.WriteLineAsync();

                        await writer.FlushAsync();

                        writer.Close();
                    }

                    using (var reader = new CustomBinaryReader(stream))
                    {
                        var result = await reader.ReadLineAsync();


                        if (!new string[] { "200 OK", "connection established" }.Any(s => result.ToLower().Contains(s.ToLower())))
                        {
                            throw new Exception("Upstream proxy failed to create a secure tunnel");
                        }

                        await reader.ReadAllLinesAsync();
                    }
                }
                else
                {
                    client = new TcpClient();
                    client.Client.Bind(upStreamEndPoint);
                    await client.ConnectAsync(remoteHostName, remotePort);

                    stream = client.GetStream();
                }

                try
                {
                    sslStream = new SslStream(stream, true, remoteCertificateValidationCallback,
                                              localCertificateSelectionCallback);

                    await sslStream.AuthenticateAsClientAsync(remoteHostName, null, supportedSslProtocols, false);

                    stream = sslStream;
                }
                catch
                {
                    sslStream?.Dispose();

                    throw;
                }
            }
            else
            {
                if (externalHttpProxy != null && externalHttpProxy.HostName != remoteHostName)
                {
                    client = new TcpClient();
                    client.Client.Bind(upStreamEndPoint);
                    await client.ConnectAsync(externalHttpProxy.HostName, externalHttpProxy.Port);

                    stream = client.GetStream();
                }
                else
                {
                    client = new TcpClient();
                    client.Client.Bind(upStreamEndPoint);
                    await client.ConnectAsync(remoteHostName, remotePort);

                    stream = client.GetStream();
                }
            }

            client.ReceiveTimeout = connectionTimeOutSeconds * 1000;
            client.SendTimeout    = connectionTimeOutSeconds * 1000;

            stream.ReadTimeout  = connectionTimeOutSeconds * 1000;
            stream.WriteTimeout = connectionTimeOutSeconds * 1000;


            return(new TcpConnection()
            {
                UpStreamHttpProxy = externalHttpProxy,
                UpStreamHttpsProxy = externalHttpsProxy,
                HostName = remoteHostName,
                Port = remotePort,
                IsHttps = isHttps,
                TcpClient = client,
                StreamReader = new CustomBinaryReader(stream),
                Stream = stream,
                Version = httpVersion
            });
        }
        /// <summary>
        /// This is called when client is aware of proxy
        /// So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
        /// </summary>
        /// <param name="endPoint"></param>
        /// <param name="tcpClient"></param>
        /// <returns></returns>
        private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient)
        {
            bool disposed = false;

            var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);

            var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
            var clientStreamWriter = new HttpResponseWriter(clientStream);

            Uri httpRemoteUri;

            try
            {
                //read the first line HTTP command
                string httpCmd = await clientStreamReader.ReadLineAsync();

                if (string.IsNullOrEmpty(httpCmd))
                {
                    return;
                }

                string  httpMethod;
                string  httpUrl;
                Version version;
                Request.ParseRequestLine(httpCmd, out httpMethod, out httpUrl, out version);

                httpRemoteUri = httpMethod == "CONNECT" ? new Uri("http://" + httpUrl) : new Uri(httpUrl);

                //filter out excluded host names
                bool excluded = false;

                if (endPoint.ExcludedHttpsHostNameRegex != null)
                {
                    excluded = endPoint.ExcludedHttpsHostNameRegexList.Any(x => x.IsMatch(httpRemoteUri.Host));
                }

                if (endPoint.IncludedHttpsHostNameRegex != null)
                {
                    excluded = !endPoint.IncludedHttpsHostNameRegexList.Any(x => x.IsMatch(httpRemoteUri.Host));
                }

                ConnectRequest connectRequest = null;

                //Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request)
                if (httpMethod == "CONNECT")
                {
                    connectRequest = new ConnectRequest
                    {
                        RequestUri         = httpRemoteUri,
                        OriginalRequestUrl = httpUrl,
                        HttpVersion        = version,
                        Method             = httpMethod,
                    };

                    await HeaderParser.ReadHeaders(clientStreamReader, connectRequest.RequestHeaders);

                    var connectArgs = new TunnelConnectSessionEventArgs(endPoint);
                    connectArgs.WebSession.Request       = connectRequest;
                    connectArgs.ProxyClient.TcpClient    = tcpClient;
                    connectArgs.ProxyClient.ClientStream = clientStream;

                    if (TunnelConnectRequest != null)
                    {
                        await TunnelConnectRequest.InvokeParallelAsync(this, connectArgs, ExceptionFunc);
                    }

                    if (!excluded && await CheckAuthorization(clientStreamWriter, connectArgs) == false)
                    {
                        if (TunnelConnectResponse != null)
                        {
                            await TunnelConnectResponse.InvokeParallelAsync(this, connectArgs, ExceptionFunc);
                        }

                        return;
                    }

                    //write back successfull CONNECT response
                    connectArgs.WebSession.Response = ConnectResponse.CreateSuccessfullConnectResponse(version);
                    await clientStreamWriter.WriteResponseAsync(connectArgs.WebSession.Response);

                    var clientHelloInfo = await SslTools.GetClientHelloInfo(clientStream);

                    bool isClientHello = clientHelloInfo != null;
                    if (isClientHello)
                    {
                        connectRequest.ClientHelloInfo = clientHelloInfo;
                    }

                    if (TunnelConnectResponse != null)
                    {
                        connectArgs.IsHttpsConnect = isClientHello;
                        await TunnelConnectResponse.InvokeParallelAsync(this, connectArgs, ExceptionFunc);
                    }

                    if (!excluded && isClientHello)
                    {
                        httpRemoteUri             = new Uri("https://" + httpUrl);
                        connectRequest.RequestUri = httpRemoteUri;

                        SslStream sslStream = null;

                        try
                        {
                            var alpnStream = AlpnEnabled ? (Stream) new ServerHelloAlpnAdderStream(clientStream) : clientStream;
                            sslStream = new SslStream(alpnStream);

                            string certName = HttpHelper.GetWildCardDomainName(httpRemoteUri.Host);

                            var certificate = endPoint.GenericCertificate ?? CertificateManager.CreateCertificate(certName, false);

                            //Successfully managed to authenticate the client using the fake certificate
                            await sslStream.AuthenticateAsServerAsync(certificate, false, SupportedSslProtocols, false);

                            //HTTPS server created - we can now decrypt the client's traffic
                            clientStream = new CustomBufferedStream(sslStream, BufferSize);

                            clientStreamReader.Dispose();
                            clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
                            clientStreamWriter = new HttpResponseWriter(clientStream);
                        }
                        catch
                        {
                            sslStream?.Dispose();
                            return;
                        }

                        //Now read the actual HTTPS request line
                        httpCmd = await clientStreamReader.ReadLineAsync();
                    }
                    //Hostname is excluded or it is not an HTTPS connect
                    else
                    {
                        //create new connection
                        using (var connection = await GetServerConnection(connectArgs, true))
                        {
                            if (isClientHello)
                            {
                                if (clientStream.Available > 0)
                                {
                                    //send the buffered data
                                    var data = new byte[clientStream.Available];
                                    await clientStream.ReadAsync(data, 0, data.Length);

                                    await connection.Stream.WriteAsync(data, 0, data.Length);

                                    await connection.Stream.FlushAsync();
                                }

                                var serverHelloInfo = await SslTools.GetServerHelloInfo(connection.Stream);

                                ((ConnectResponse)connectArgs.WebSession.Response).ServerHelloInfo = serverHelloInfo;
                            }

                            await TcpHelper.SendRaw(clientStream, connection.Stream,
                                                    (buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); });

                            UpdateServerConnectionCount(false);
                        }

                        return;
                    }
                }

                //Now create the request
                disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
                                                          httpRemoteUri.Scheme == UriSchemeHttps?httpRemoteUri.Host : null, endPoint, connectRequest);
            }
            catch (Exception e)
            {
                ExceptionFunc(new Exception("Error whilst authorizing request", e));
            }
            finally
            {
                if (!disposed)
                {
                    Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
                }
            }
        }
        /// <summary>
        /// This is called when client is aware of proxy
        /// So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
        /// </summary>
        /// <param name="endPoint"></param>
        /// <param name="tcpClient"></param>
        /// <returns></returns>
        private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient)
        {
            var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);

            var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
            var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);

            try
            {
                string connectHostname = null;

                ConnectRequest connectRequest = null;

                //Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request)
                if (await HttpHelper.IsConnectMethod(clientStream) == 1)
                {
                    //read the first line HTTP command
                    string httpCmd = await clientStreamReader.ReadLineAsync();

                    if (string.IsNullOrEmpty(httpCmd))
                    {
                        return;
                    }

                    Request.ParseRequestLine(httpCmd, out string _, out string httpUrl, out var version);

                    var httpRemoteUri = new Uri("http://" + httpUrl);
                    connectHostname = httpRemoteUri.Host;

                    //filter out excluded host names
                    bool excluded = false;

                    if (endPoint.ExcludedHttpsHostNameRegex != null)
                    {
                        excluded = endPoint.ExcludedHttpsHostNameRegexList.Any(x => x.IsMatch(connectHostname));
                    }

                    if (endPoint.IncludedHttpsHostNameRegex != null)
                    {
                        excluded = !endPoint.IncludedHttpsHostNameRegexList.Any(x => x.IsMatch(connectHostname));
                    }

                    if (endPoint.BeforeTunnelConnect != null)
                    {
                        excluded = await endPoint.BeforeTunnelConnect(connectHostname);
                    }

                    connectRequest = new ConnectRequest
                    {
                        RequestUri  = httpRemoteUri,
                        OriginalUrl = httpUrl,
                        HttpVersion = version,
                    };

                    await HeaderParser.ReadHeaders(clientStreamReader, connectRequest.Headers);

                    var connectArgs = new TunnelConnectSessionEventArgs(BufferSize, endPoint, connectRequest, ExceptionFunc);
                    connectArgs.ProxyClient.TcpClient    = tcpClient;
                    connectArgs.ProxyClient.ClientStream = clientStream;

                    await endPoint.InvokeTunnectConnectRequest(this, connectArgs, ExceptionFunc);


                    if (await CheckAuthorization(clientStreamWriter, connectArgs) == false)
                    {
                        await endPoint.InvokeTunnectConnectResponse(this, connectArgs, ExceptionFunc);

                        return;
                    }

                    //write back successfull CONNECT response
                    var response = ConnectResponse.CreateSuccessfullConnectResponse(version);
                    response.Headers.FixProxyHeaders();
                    connectArgs.WebSession.Response = response;

                    await clientStreamWriter.WriteResponseAsync(response);

                    var clientHelloInfo = await SslTools.PeekClientHello(clientStream);

                    bool isClientHello = clientHelloInfo != null;
                    if (isClientHello)
                    {
                        connectRequest.ClientHelloInfo = clientHelloInfo;
                    }

                    await endPoint.InvokeTunnectConnectResponse(this, connectArgs, ExceptionFunc, isClientHello);

                    if (!excluded && isClientHello)
                    {
                        connectRequest.RequestUri = new Uri("https://" + httpUrl);

                        SslStream sslStream = null;

                        try
                        {
                            sslStream = new SslStream(clientStream);

                            string certName = HttpHelper.GetWildCardDomainName(connectHostname);

                            var certificate = endPoint.GenericCertificate ?? await CertificateManager.CreateCertificateAsync(certName);

                            //Successfully managed to authenticate the client using the fake certificate
                            await sslStream.AuthenticateAsServerAsync(certificate, false, SupportedSslProtocols, false);

                            //HTTPS server created - we can now decrypt the client's traffic
                            clientStream = new CustomBufferedStream(sslStream, BufferSize);

                            clientStreamReader.Dispose();
                            clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
                            clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
                        }
                        catch
                        {
                            sslStream?.Dispose();
                            return;
                        }

                        if (await HttpHelper.IsConnectMethod(clientStream) == -1)
                        {
                            // It can be for example some Google (Cloude Messaging for Chrome) magic
                            excluded = true;
                        }
                    }

                    //Hostname is excluded or it is not an HTTPS connect
                    if (excluded || !isClientHello)
                    {
                        //create new connection
                        using (var connection = await GetServerConnection(connectArgs, true))
                        {
                            if (isClientHello)
                            {
                                int available = clientStream.Available;
                                if (available > 0)
                                {
                                    //send the buffered data
                                    var data = BufferPool.GetBuffer(BufferSize);

                                    try
                                    {
                                        // clientStream.Available sbould be at most BufferSize because it is using the same buffer size
                                        await clientStream.ReadAsync(data, 0, available);

                                        await connection.StreamWriter.WriteAsync(data, 0, available, true);
                                    }
                                    finally
                                    {
                                        BufferPool.ReturnBuffer(data);
                                    }
                                }

                                var serverHelloInfo = await SslTools.PeekServerHello(connection.Stream);

                                ((ConnectResponse)connectArgs.WebSession.Response).ServerHelloInfo = serverHelloInfo;
                            }

                            await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize,
                                                    (buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); },
                                                    (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); },
                                                    ExceptionFunc);
                        }

                        return;
                    }
                }

                //Now create the request
                await HandleHttpSessionRequest(tcpClient, clientStream, clientStreamReader, clientStreamWriter, connectHostname, endPoint, connectRequest);
            }
            catch (ProxyHttpException e)
            {
                ExceptionFunc(e);
            }
            catch (IOException e)
            {
                ExceptionFunc(new Exception("Connection was aborted", e));
            }
            catch (SocketException e)
            {
                ExceptionFunc(new Exception("Could not connect", e));
            }
            catch (Exception e)
            {
                ExceptionFunc(new Exception("Error occured in whilst handling the client", e));
            }
            finally
            {
                clientStreamReader.Dispose();
                clientStream.Dispose();
            }
        }
        /// <summary>
        /// This is called when client is aware of proxy
        /// So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
        /// </summary>
        /// <param name="endPoint"></param>
        /// <param name="tcpClient"></param>
        /// <returns></returns>
        private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient)
        {
            var disposed = false;

            var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);

            var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
            var clientStreamWriter = new StreamWriter(clientStream)
            {
                NewLine = ProxyConstants.NewLine
            };

            Uri httpRemoteUri;

            try
            {
                //read the first line HTTP command
                var httpCmd = await clientStreamReader.ReadLineAsync();

                if (string.IsNullOrEmpty(httpCmd))
                {
                    return;
                }

                //break up the line into three components (method, remote URL & Http Version)
                var httpCmdSplit = httpCmd.Split(ProxyConstants.SpaceSplit, 3);

                //Find the request Verb
                var httpVerb = httpCmdSplit[0].ToUpper();

                httpRemoteUri = httpVerb == "CONNECT" ? new Uri("http://" + httpCmdSplit[1]) : new Uri(httpCmdSplit[1]);

                //parse the HTTP version
                var version = HttpHeader.Version11;
                if (httpCmdSplit.Length == 3)
                {
                    var httpVersion = httpCmdSplit[2].Trim();

                    if (string.Equals(httpVersion, "HTTP/1.0", StringComparison.OrdinalIgnoreCase))
                    {
                        version = HttpHeader.Version10;
                    }
                }

                //filter out excluded host names
                bool excluded = false;

                if (endPoint.ExcludedHttpsHostNameRegex != null)
                {
                    excluded = endPoint.ExcludedHttpsHostNameRegexList.Any(x => x.IsMatch(httpRemoteUri.Host));
                }

                if (endPoint.IncludedHttpsHostNameRegex != null)
                {
                    excluded = !endPoint.IncludedHttpsHostNameRegexList.Any(x => x.IsMatch(httpRemoteUri.Host));
                }

                List <HttpHeader> connectRequestHeaders = null;

                //Client wants to create a secure tcp tunnel (its a HTTPS request)
                if (httpVerb == "CONNECT" && !excluded &&
                    endPoint.RemoteHttpsPorts.Contains(httpRemoteUri.Port))
                {
                    httpRemoteUri         = new Uri("https://" + httpCmdSplit[1]);
                    connectRequestHeaders = new List <HttpHeader>();
                    string tmpLine;
                    while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync()))
                    {
                        var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);

                        var newHeader = new HttpHeader(header[0], header[1]);
                        connectRequestHeaders.Add(newHeader);
                    }

                    if (await CheckAuthorization(clientStreamWriter, connectRequestHeaders) == false)
                    {
                        return;
                    }

                    await WriteConnectResponse(clientStreamWriter, version);

                    SslStream sslStream = null;

                    try
                    {
                        sslStream = new SslStream(clientStream);

                        var certName = HttpHelper.GetWildCardDomainName(httpRemoteUri.Host);

                        var certificate = endPoint.GenericCertificate ??
                                          CertificateManager.CreateCertificate(certName, false);

                        //Successfully managed to authenticate the client using the fake certificate
                        await sslStream.AuthenticateAsServerAsync(certificate, false,
                                                                  SupportedSslProtocols, false);

                        //HTTPS server created - we can now decrypt the client's traffic
                        clientStream = new CustomBufferedStream(sslStream, BufferSize);

                        clientStreamReader.Dispose();
                        clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
                        clientStreamWriter = new StreamWriter(clientStream)
                        {
                            NewLine = ProxyConstants.NewLine
                        };
                    }
                    catch
                    {
                        sslStream?.Dispose();
                        return;
                    }

                    //Now read the actual HTTPS request line
                    httpCmd = await clientStreamReader.ReadLineAsync();
                }
                //Sorry cannot do a HTTPS request decrypt to port 80 at this time
                else if (httpVerb == "CONNECT")
                {
                    //Siphon out CONNECT request headers
                    await clientStreamReader.ReadAndIgnoreAllLinesAsync();

                    //write back successfull CONNECT response
                    await WriteConnectResponse(clientStreamWriter, version);

                    await TcpHelper.SendRaw(this,
                                            httpRemoteUri.Host, httpRemoteUri.Port,
                                            null, version, null,
                                            false,
                                            clientStream, tcpConnectionFactory);

                    return;
                }

                //Now create the request
                disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
                                                          httpRemoteUri.Scheme == Uri.UriSchemeHttps?httpRemoteUri.Host : null, endPoint,
                                                          connectRequestHeaders);
            }
            catch (Exception e)
            {
                ExceptionFunc(new Exception("Error whilst authorizing request", e));
            }
            finally
            {
                if (!disposed)
                {
                    Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
                }
            }
        }
        /// <summary>
        /// This is the core request handler method for a particular connection from client
        /// Will create new session (request/response) sequence until
        /// client/server abruptly terminates connection or by normal HTTP termination
        /// </summary>
        /// <param name="client"></param>
        /// <param name="httpCmd"></param>
        /// <param name="clientStream"></param>
        /// <param name="clientStreamReader"></param>
        /// <param name="clientStreamWriter"></param>
        /// <param name="httpsHostName"></param>
        /// <param name="endPoint"></param>
        /// <param name="connectHeaders"></param>
        /// <returns></returns>
        private async Task <bool> HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
                                                           CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName,
                                                           ProxyEndPoint endPoint, List <HttpHeader> connectHeaders)
        {
            bool disposed = false;

            TcpConnection connection = null;

            //Loop through each subsequest request on this particular client connection
            //(assuming HTTP connection is kept alive by client)
            while (true)
            {
                if (string.IsNullOrEmpty(httpCmd))
                {
                    break;
                }

                var args = new SessionEventArgs(BufferSize, HandleHttpSessionResponse)
                {
                    ProxyClient = { TcpClient = client },
                    WebSession  = { ConnectHeaders = connectHeaders }
                };

                args.WebSession.ProcessId = new Lazy <int>(() =>
                {
                    var remoteEndPoint = (IPEndPoint)args.ProxyClient.TcpClient.Client.RemoteEndPoint;

                    //If client is localhost get the process id
                    if (NetworkHelper.IsLocalIpAddress(remoteEndPoint.Address))
                    {
                        return(NetworkHelper.GetProcessIdFromPort(remoteEndPoint.Port, endPoint.IpV6Enabled));
                    }

                    //can't access process Id of remote request from remote machine
                    return(-1);
                });

                try
                {
                    //break up the line into three components (method, remote URL & Http Version)
                    var httpCmdSplit = httpCmd.Split(ProxyConstants.SpaceSplit, 3);

                    var httpMethod = httpCmdSplit[0];

                    //find the request HTTP version
                    var httpVersion = HttpHeader.Version11;
                    if (httpCmdSplit.Length == 3)
                    {
                        var httpVersionString = httpCmdSplit[2].Trim();

                        if (string.Equals(httpVersionString, "HTTP/1.0", StringComparison.OrdinalIgnoreCase))
                        {
                            httpVersion = HttpHeader.Version10;
                        }
                    }

                    //Read the request headers in to unique and non-unique header collections
                    await HeaderParser.ReadHeaders(clientStreamReader, args.WebSession.Request.NonUniqueRequestHeaders, args.WebSession.Request.RequestHeaders);

                    var httpRemoteUri = new Uri(httpsHostName == null
                        ? httpCmdSplit[1]
                        : string.Concat("https://", args.WebSession.Request.Host ?? httpsHostName, httpCmdSplit[1]));

                    args.WebSession.Request.RequestUri = httpRemoteUri;

                    args.WebSession.Request.Method      = httpMethod.Trim().ToUpper();
                    args.WebSession.Request.HttpVersion = httpVersion;
                    args.ProxyClient.ClientStream       = clientStream;
                    args.ProxyClient.ClientStreamReader = clientStreamReader;
                    args.ProxyClient.ClientStreamWriter = clientStreamWriter;

                    if (httpsHostName == null &&
                        await CheckAuthorization(clientStreamWriter,
                                                 args.WebSession.Request.RequestHeaders.Values) == false)
                    {
                        args.Dispose();
                        break;
                    }

                    PrepareRequestHeaders(args.WebSession.Request.RequestHeaders, args.WebSession);
                    args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority;

                    //if win auth is enabled
                    //we need a cache of request body
                    //so that we can send it after authentication in WinAuthHandler.cs
                    if (EnableWinAuth &&
                        !RunTime.IsRunningOnMono &&
                        args.WebSession.Request.HasBody)
                    {
                        await args.GetRequestBody();
                    }

                    //If user requested interception do it
                    if (BeforeRequest != null)
                    {
                        await BeforeRequest.InvokeParallelAsync(this, args);
                    }

                    //if upgrading to websocket then relay the requet without reading the contents
                    if (args.WebSession.Request.UpgradeToWebSocket)
                    {
                        await TcpHelper.SendRaw(this,
                                                httpRemoteUri.Host, httpRemoteUri.Port,
                                                httpCmd, httpVersion, args.WebSession.Request.RequestHeaders, args.IsHttps,
                                                clientStream, tcpConnectionFactory, connection);

                        args.Dispose();
                        break;
                    }

                    if (connection == null)
                    {
                        connection = await GetServerConnection(args);
                    }

                    //construct the web request that we are going to issue on behalf of the client.
                    disposed = await HandleHttpSessionRequestInternal(connection, args, false);

                    if (disposed)
                    {
                        //already disposed inside above method
                        args.Dispose();
                        break;
                    }

                    if (args.WebSession.Request.CancelRequest)
                    {
                        args.Dispose();
                        break;
                    }

                    //if connection is closing exit
                    if (args.WebSession.Response.ResponseKeepAlive == false)
                    {
                        args.Dispose();
                        break;
                    }

                    args.Dispose();

                    // read the next request
                    httpCmd = await clientStreamReader.ReadLineAsync();
                }
                catch (Exception e)
                {
                    ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request", e, args));
                    break;
                }
            }

            if (!disposed)
            {
                Dispose(clientStream, clientStreamReader, clientStreamWriter, connection);
            }

            return(true);
        }
        /// <summary>
        /// Creates a TCP connection to server
        /// </summary>
        /// <param name="bufferSize"></param>
        /// <param name="connectionTimeOutSeconds"></param>
        /// <param name="remoteHostName"></param>
        /// <param name="httpCmd"></param>
        /// <param name="httpVersion"></param>
        /// <param name="isHttps"></param>
        /// <param name="remotePort"></param>
        /// <param name="supportedSslProtocols"></param>
        /// <param name="remoteCertificateValidationCallback"></param>
        /// <param name="localCertificateSelectionCallback"></param>
        /// <param name="externalHttpProxy"></param>
        /// <param name="externalHttpsProxy"></param>
        /// <param name="clientStream"></param>
        /// <returns></returns>
        internal async Task <TcpConnection> CreateClient(int bufferSize, int connectionTimeOutSeconds,
                                                         string remoteHostName, int remotePort, Version httpVersion,
                                                         bool isHttps, SslProtocols supportedSslProtocols,
                                                         RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
                                                         ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy,
                                                         Stream clientStream)
        {
            TcpClient client;
            Stream    stream;

            if (isHttps)
            {
                SslStream sslStream = null;

                //If this proxy uses another external proxy then create a tunnel request for HTTPS connections
                if (externalHttpsProxy != null)
                {
                    client = new TcpClient(externalHttpsProxy.HostName, externalHttpsProxy.Port);
                    stream = client.GetStream();

                    using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true))
                    {
                        await writer.WriteLineAsync(string.Format("CONNECT {0}:{1} {2}", remoteHostName, remotePort, httpVersion));

                        await writer.WriteLineAsync(string.Format("Host: {0}:{1}", remoteHostName, remotePort));

                        await writer.WriteLineAsync("Connection: Keep-Alive");

                        await writer.WriteLineAsync();

                        await writer.FlushAsync();

                        writer.Close();
                    }

                    using (var reader = new CustomBinaryReader(stream))
                    {
                        var result = await reader.ReadLineAsync();

                        if (!result.ToLower().Contains("200 connection established"))
                        {
                            throw new Exception("Upstream proxy failed to create a secure tunnel");
                        }

                        await reader.ReadAllLinesAsync();
                    }
                }
                else
                {
                    client = new TcpClient(remoteHostName, remotePort);
                    stream = client.GetStream();
                }

                try
                {
                    sslStream = new SslStream(stream, true, remoteCertificateValidationCallback,
                                              localCertificateSelectionCallback);

                    await sslStream.AuthenticateAsClientAsync(remoteHostName, null, supportedSslProtocols, false);

                    stream = sslStream;
                }
                catch
                {
                    if (sslStream != null)
                    {
                        sslStream.Dispose();
                    }

                    throw;
                }
            }
            else
            {
                if (externalHttpProxy != null)
                {
                    client = new TcpClient(externalHttpProxy.HostName, externalHttpProxy.Port);
                    stream = client.GetStream();
                }
                else
                {
                    client = new TcpClient(remoteHostName, remotePort);
                    stream = client.GetStream();
                }
            }

            client.ReceiveTimeout = connectionTimeOutSeconds * 1000;
            client.SendTimeout    = connectionTimeOutSeconds * 1000;

            stream.ReadTimeout  = connectionTimeOutSeconds * 1000;
            stream.WriteTimeout = connectionTimeOutSeconds * 1000;

            return(new TcpConnection()
            {
                HostName = remoteHostName,
                port = remotePort,
                IsHttps = isHttps,
                TcpClient = client,
                StreamReader = new CustomBinaryReader(stream),
                Stream = stream
            });
        }
Example #19
0
        private static async Task <TcpConnection> CreateClient(string hostname, int port, bool isHttps, Version version)
        {
            TcpClient client;
            Stream    stream;

            if (isHttps)
            {
                CustomSslStream sslStream = null;

                if (ProxyServer.UpStreamHttpsProxy != null)
                {
                    client = new TcpClient(ProxyServer.UpStreamHttpsProxy.HostName, ProxyServer.UpStreamHttpsProxy.Port);
                    stream = (Stream)client.GetStream();

                    using (var writer = new StreamWriter(stream, Encoding.ASCII, Constants.BUFFER_SIZE, true))
                    {
                        await writer.WriteLineAsync(string.Format("CONNECT {0}:{1} {2}", hostname, port, version)).ConfigureAwait(false);

                        await writer.WriteLineAsync(string.Format("Host: {0}:{1}", hostname, port)).ConfigureAwait(false);

                        await writer.WriteLineAsync("Connection: Keep-Alive").ConfigureAwait(false);

                        await writer.WriteLineAsync().ConfigureAwait(false);

                        await writer.FlushAsync().ConfigureAwait(false);

                        writer.Close();
                    }

                    using (var reader = new CustomBinaryReader(stream))
                    {
                        var result = await reader.ReadLineAsync().ConfigureAwait(false);

                        if (!result.ToLower().Contains("200 connection established"))
                        {
                            throw new Exception("Upstream proxy failed to create a secure tunnel");
                        }

                        await reader.ReadAllLinesAsync().ConfigureAwait(false);
                    }
                }
                else
                {
                    client = new TcpClient(hostname, port);
                    stream = (Stream)client.GetStream();
                }

                try
                {
                    sslStream = new CustomSslStream(stream, true, new RemoteCertificateValidationCallback(ProxyServer.ValidateServerCertificate),
                                                    new LocalCertificateSelectionCallback(ProxyServer.SelectClientCertificate));
                    await sslStream.AuthenticateAsClientAsync(hostname, null, Constants.SupportedProtocols, false).ConfigureAwait(false);

                    stream = (Stream)sslStream;
                }
                catch
                {
                    if (sslStream != null)
                    {
                        sslStream.Dispose();
                    }
                    throw;
                }
            }
            else
            {
                if (ProxyServer.UpStreamHttpProxy != null)
                {
                    client = new TcpClient(ProxyServer.UpStreamHttpProxy.HostName, ProxyServer.UpStreamHttpProxy.Port);
                    stream = (Stream)client.GetStream();
                }
                else
                {
                    client = new TcpClient(hostname, port);
                    stream = (Stream)client.GetStream();
                }
            }

            return(new TcpConnection()
            {
                HostName = hostname,
                port = port,
                IsHttps = isHttps,
                TcpClient = client,
                StreamReader = new CustomBinaryReader(stream),
                Stream = stream,
                Version = version
            });
        }
Example #20
0
        /// <summary>
        /// Create connection to a particular host/port optionally with SSL and a particular HTTP version
        /// </summary>
        /// <param name="hostname"></param>
        /// <param name="port"></param>
        /// <param name="isHttps"></param>
        /// <param name="version"></param>
        /// <returns></returns>
        private async Task <TcpConnectionCache> CreateClient(string hostname, int port, bool isHttps, Version version,
                                                             ExternalProxy upStreamHttpProxy, ExternalProxy upStreamHttpsProxy, int bufferSize, SslProtocols supportedSslProtocols,
                                                             RemoteCertificateValidationCallback remoteCertificateValidationCallBack, LocalCertificateSelectionCallback localCertificateSelectionCallback)
        {
            TcpClient client;
            Stream    stream;

            if (isHttps)
            {
                SslStream sslStream = null;

                //If this proxy uses another external proxy then create a tunnel request for HTTPS connections
                if (upStreamHttpsProxy != null)
                {
                    client = new TcpClient(upStreamHttpsProxy.HostName, upStreamHttpsProxy.Port);
                    stream = (Stream)client.GetStream();

                    using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true))
                    {
                        await writer.WriteLineAsync(string.Format("CONNECT {0}:{1} {2}", hostname, port, version));

                        await writer.WriteLineAsync(string.Format("Host: {0}:{1}", hostname, port));

                        await writer.WriteLineAsync("Connection: Keep-Alive");

                        await writer.WriteLineAsync();

                        await writer.FlushAsync();

                        writer.Close();
                    }

                    using (var reader = new CustomBinaryReader(stream))
                    {
                        var result = await reader.ReadLineAsync();

                        if (!result.ToLower().Contains("200 connection established"))
                        {
                            throw new Exception("Upstream proxy failed to create a secure tunnel");
                        }

                        await reader.ReadAllLinesAsync();
                    }
                }
                else
                {
                    client = new TcpClient(hostname, port);
                    stream = (Stream)client.GetStream();
                }

                try
                {
                    sslStream = new SslStream(stream, true, remoteCertificateValidationCallBack,
                                              localCertificateSelectionCallback);
                    await sslStream.AuthenticateAsClientAsync(hostname, null, supportedSslProtocols, false);

                    stream = (Stream)sslStream;
                }
                catch
                {
                    if (sslStream != null)
                    {
                        sslStream.Dispose();
                    }
                    throw;
                }
            }
            else
            {
                if (upStreamHttpProxy != null)
                {
                    client = new TcpClient(upStreamHttpProxy.HostName, upStreamHttpProxy.Port);
                    stream = (Stream)client.GetStream();
                }
                else
                {
                    client = new TcpClient(hostname, port);
                    stream = (Stream)client.GetStream();
                }
            }

            return(new TcpConnectionCache()
            {
                HostName = hostname,
                port = port,
                IsHttps = isHttps,
                TcpClient = client,
                StreamReader = new CustomBinaryReader(stream),
                Stream = stream,
                Version = version
            });
        }
Example #21
0
        /// <summary>
        /// This is the core request handler method for a particular connection from client
        /// </summary>
        /// <param name="client"></param>
        /// <param name="httpCmd"></param>
        /// <param name="clientStream"></param>
        /// <param name="clientStreamReader"></param>
        /// <param name="clientStreamWriter"></param>
        /// <param name="httpsHostName"></param>
        /// <returns></returns>
        private async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
                                                    CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName, ProxyEndPoint endPoint, List <HttpHeader> connectHeaders, ExternalProxy customUpStreamHttpProxy = null, ExternalProxy customUpStreamHttpsProxy = null)
        {
            TcpConnection connection = null;

            //Loop through each subsequest request on this particular client connection
            //(assuming HTTP connection is kept alive by client)
            while (true)
            {
                if (string.IsNullOrEmpty(httpCmd))
                {
                    Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
                    break;
                }

                var args = new SessionEventArgs(BUFFER_SIZE, HandleHttpSessionResponse);
                args.ProxyClient.TcpClient     = client;
                args.WebSession.ConnectHeaders = connectHeaders;

                args.WebSession.ProcessId = new Lazy <int>(() =>
                {
                    var remoteEndPoint = (IPEndPoint)args.ProxyClient.TcpClient.Client.RemoteEndPoint;

                    //If client is localhost get the process id
                    if (NetworkHelper.IsLocalIpAddress(remoteEndPoint.Address))
                    {
                        return(NetworkHelper.GetProcessIdFromPort(remoteEndPoint.Port, endPoint.IpV6Enabled));
                    }

                    //can't access process Id of remote request from remote machine
                    return(-1);
                });
                try
                {
                    //break up the line into three components (method, remote URL & Http Version)
                    var httpCmdSplit = httpCmd.Split(ProxyConstants.SpaceSplit, 3);

                    var httpMethod = httpCmdSplit[0];

                    //find the request HTTP version
                    Version httpVersion = new Version(1, 1);
                    if (httpCmdSplit.Length == 3)
                    {
                        var httpVersionString = httpCmdSplit[2].ToLower().Trim();

                        if (httpVersionString == "http/1.0")
                        {
                            httpVersion = new Version(1, 0);
                        }
                    }


                    //Read the request headers in to unique and non-unique header collections
                    string tmpLine;
                    while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync()))
                    {
                        var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);

                        var newHeader = new HttpHeader(header[0], header[1]);

                        //if header exist in non-unique header collection add it there
                        if (args.WebSession.Request.NonUniqueRequestHeaders.ContainsKey(newHeader.Name))
                        {
                            args.WebSession.Request.NonUniqueRequestHeaders[newHeader.Name].Add(newHeader);
                        }
                        //if header is alread in unique header collection then move both to non-unique collection
                        else if (args.WebSession.Request.RequestHeaders.ContainsKey(newHeader.Name))
                        {
                            var existing = args.WebSession.Request.RequestHeaders[newHeader.Name];

                            var nonUniqueHeaders = new List <HttpHeader>();

                            nonUniqueHeaders.Add(existing);
                            nonUniqueHeaders.Add(newHeader);

                            args.WebSession.Request.NonUniqueRequestHeaders.Add(newHeader.Name, nonUniqueHeaders);
                            args.WebSession.Request.RequestHeaders.Remove(newHeader.Name);
                        }
                        //add to unique header collection
                        else
                        {
                            args.WebSession.Request.RequestHeaders.Add(newHeader.Name, newHeader);
                        }
                    }

                    var httpRemoteUri = new Uri(httpsHostName == null ? httpCmdSplit[1]
                        : (string.Concat("https://", args.WebSession.Request.Host == null ?
                                         httpsHostName : args.WebSession.Request.Host, httpCmdSplit[1])));

                    args.WebSession.Request.RequestUri = httpRemoteUri;

                    args.WebSession.Request.Method      = httpMethod.Trim().ToUpper();
                    args.WebSession.Request.HttpVersion = httpVersion;
                    args.ProxyClient.ClientStream       = clientStream;
                    args.ProxyClient.ClientStreamReader = clientStreamReader;
                    args.ProxyClient.ClientStreamWriter = clientStreamWriter;

                    if (httpsHostName == null && (await CheckAuthorization(clientStreamWriter, args.WebSession.Request.RequestHeaders.Values) == false))
                    {
                        Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
                        break;
                    }

                    PrepareRequestHeaders(args.WebSession.Request.RequestHeaders, args.WebSession);
                    args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority;

                    //If user requested interception do it
                    if (BeforeRequest != null)
                    {
                        Delegate[] invocationList = BeforeRequest.GetInvocationList();
                        Task[]     handlerTasks   = new Task[invocationList.Length];

                        for (int i = 0; i < invocationList.Length; i++)
                        {
                            handlerTasks[i] = ((Func <object, SessionEventArgs, Task>)invocationList[i])(null, args);
                        }

                        await Task.WhenAll(handlerTasks);
                    }

                    //if upgrading to websocket then relay the requet without reading the contents
                    if (args.WebSession.Request.UpgradeToWebSocket)
                    {
                        await TcpHelper.SendRaw(BUFFER_SIZE, ConnectionTimeOutSeconds, httpRemoteUri.Host, httpRemoteUri.Port,
                                                httpCmd, httpVersion, args.WebSession.Request.RequestHeaders, args.IsHttps,
                                                SupportedSslProtocols, new RemoteCertificateValidationCallback(ValidateServerCertificate),
                                                new LocalCertificateSelectionCallback(SelectClientCertificate),
                                                clientStream, tcpConnectionFactory, UpStreamEndPoint);

                        Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
                        break;
                    }

                    //construct the web request that we are going to issue on behalf of the client.
                    await HandleHttpSessionRequestInternal(connection, args, customUpStreamHttpProxy, customUpStreamHttpsProxy, false).ConfigureAwait(false);


                    if (args.WebSession.Request.CancelRequest)
                    {
                        break;
                    }

                    //if connection is closing exit
                    if (args.WebSession.Response.ResponseKeepAlive == false)
                    {
                        break;
                    }

                    // read the next request
                    httpCmd = await clientStreamReader.ReadLineAsync();
                }
                catch (Exception e)
                {
                    ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request", e, args));
                    Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
                    break;
                }
            }

            if (connection != null)
            {
                //dispose
                connection.Dispose();
            }
        }
        /// <summary>
        /// This is the core request handler method for a particular connection from client
        /// Will create new session (request/response) sequence until
        /// client/server abruptly terminates connection or by normal HTTP termination
        /// </summary>
        /// <param name="client"></param>
        /// <param name="httpCmd"></param>
        /// <param name="clientStream"></param>
        /// <param name="clientStreamReader"></param>
        /// <param name="clientStreamWriter"></param>
        /// <param name="httpsConnectHostname"></param>
        /// <param name="endPoint"></param>
        /// <param name="connectRequest"></param>
        /// <param name="isTransparentEndPoint"></param>
        /// <returns></returns>
        private async Task <bool> HandleHttpSessionRequest(TcpClient client, string httpCmd, CustomBufferedStream clientStream,
                                                           CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, string httpsConnectHostname,
                                                           ProxyEndPoint endPoint, ConnectRequest connectRequest, bool isTransparentEndPoint = false)
        {
            bool disposed = false;

            TcpConnection connection = null;

            //Loop through each subsequest request on this particular client connection
            //(assuming HTTP connection is kept alive by client)
            while (true)
            {
                if (string.IsNullOrEmpty(httpCmd))
                {
                    break;
                }

                var args = new SessionEventArgs(BufferSize, endPoint, HandleHttpSessionResponse)
                {
                    ProxyClient = { TcpClient = client },
                    WebSession  = { ConnectRequest = connectRequest }
                };

                try
                {
                    string  httpMethod;
                    string  httpUrl;
                    Version version;
                    Request.ParseRequestLine(httpCmd, out httpMethod, out httpUrl, out version);

                    //Read the request headers in to unique and non-unique header collections
                    await HeaderParser.ReadHeaders(clientStreamReader, args.WebSession.Request.RequestHeaders);

                    var httpRemoteUri = new Uri(httpsConnectHostname == null
                        ? isTransparentEndPoint ? string.Concat("http://", args.WebSession.Request.Host, httpUrl) : httpUrl
                        : string.Concat("https://", args.WebSession.Request.Host ?? httpsConnectHostname, httpUrl));

                    args.WebSession.Request.RequestUri         = httpRemoteUri;
                    args.WebSession.Request.OriginalRequestUrl = httpUrl;

                    args.WebSession.Request.Method      = httpMethod;
                    args.WebSession.Request.HttpVersion = version;
                    args.ProxyClient.ClientStream       = clientStream;
                    args.ProxyClient.ClientStreamReader = clientStreamReader;
                    args.ProxyClient.ClientStreamWriter = clientStreamWriter;

                    //proxy authorization check
                    if (httpsConnectHostname == null && await CheckAuthorization(clientStreamWriter, args) == false)
                    {
                        args.Dispose();
                        break;
                    }

                    PrepareRequestHeaders(args.WebSession.Request.RequestHeaders);
                    args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority;

#if NET45
                    //if win auth is enabled
                    //we need a cache of request body
                    //so that we can send it after authentication in WinAuthHandler.cs
                    if (EnableWinAuth && !RunTime.IsRunningOnMono && args.WebSession.Request.HasBody)
                    {
                        await args.GetRequestBody();
                    }
#endif

                    //If user requested interception do it
                    if (BeforeRequest != null)
                    {
                        await BeforeRequest.InvokeParallelAsync(this, args, ExceptionFunc);
                    }

                    if (args.WebSession.Request.CancelRequest)
                    {
                        args.Dispose();
                        break;
                    }

                    //create a new connection if hostname changes
                    if (connection != null && !connection.HostName.Equals(args.WebSession.Request.RequestUri.Host, StringComparison.OrdinalIgnoreCase))
                    {
                        connection.Dispose();
                        UpdateServerConnectionCount(false);
                        connection = null;
                    }

                    if (connection == null)
                    {
                        connection = await GetServerConnection(args, false);
                    }

                    //if upgrading to websocket then relay the requet without reading the contents
                    if (args.WebSession.Request.UpgradeToWebSocket)
                    {
                        //prepare the prefix content
                        var    requestHeaders = args.WebSession.Request.RequestHeaders;
                        byte[] requestBytes;
                        using (var ms = new MemoryStream())
                            using (var writer = new HttpRequestWriter(ms))
                            {
                                writer.WriteLine(httpCmd);
                                writer.WriteHeaders(requestHeaders);
                                requestBytes = ms.ToArray();
                            }

                        await connection.Stream.WriteAsync(requestBytes, 0, requestBytes.Length);

                        string httpStatus = await connection.StreamReader.ReadLineAsync();

                        Version responseVersion;
                        int     responseStatusCode;
                        string  responseStatusDescription;
                        Response.ParseResponseLine(httpStatus, out responseVersion, out responseStatusCode, out responseStatusDescription);
                        args.WebSession.Response.HttpVersion               = responseVersion;
                        args.WebSession.Response.ResponseStatusCode        = responseStatusCode;
                        args.WebSession.Response.ResponseStatusDescription = responseStatusDescription;

                        await HeaderParser.ReadHeaders(connection.StreamReader, args.WebSession.Response.ResponseHeaders);

                        await clientStreamWriter.WriteResponseAsync(args.WebSession.Response);

                        //If user requested call back then do it
                        if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked)
                        {
                            await BeforeResponse.InvokeParallelAsync(this, args, ExceptionFunc);
                        }

                        await TcpHelper.SendRaw(clientStream, connection.Stream,
                                                (buffer, offset, count) => { args.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); });

                        args.Dispose();
                        break;
                    }

                    //construct the web request that we are going to issue on behalf of the client.
                    disposed = await HandleHttpSessionRequestInternal(connection, args, false);

                    if (disposed)
                    {
                        //already disposed inside above method
                        args.Dispose();
                        break;
                    }

                    //if connection is closing exit
                    if (args.WebSession.Response.ResponseKeepAlive == false)
                    {
                        args.Dispose();
                        break;
                    }

                    args.Dispose();

                    // read the next request
                    httpCmd = await clientStreamReader.ReadLineAsync();
                }
                catch (Exception e)
                {
                    ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request", e, args));
                    break;
                }
            }

            if (!disposed)
            {
                Dispose(clientStream, clientStreamReader, clientStreamWriter, connection);
            }

            return(true);
        }
Example #23
0
        /// <summary>
        /// Creates a TCP connection to server
        /// </summary>
        /// <param name="server"></param>
        /// <param name="remoteHostName"></param>
        /// <param name="remotePort"></param>
        /// <param name="httpVersion"></param>
        /// <param name="isHttps"></param>
        /// <param name="externalHttpProxy"></param>
        /// <param name="externalHttpsProxy"></param>
        /// <returns></returns>
        internal async Task <TcpConnection> CreateClient(ProxyServer server,
                                                         string remoteHostName, int remotePort, Version httpVersion,
                                                         bool isHttps,
                                                         ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy)
        {
            bool useHttpProxy = false;

            //check if external proxy is set for HTTP
            if (!isHttps && externalHttpProxy != null &&
                !(externalHttpProxy.HostName == remoteHostName &&
                  externalHttpProxy.Port == remotePort))
            {
                useHttpProxy = true;

                //check if we need to ByPass
                if (externalHttpProxy.BypassLocalhost &&
                    NetworkHelper.IsLocalIpAddress(remoteHostName))
                {
                    useHttpProxy = false;
                }
            }

            bool useHttpsProxy = false;

            //check if external proxy is set for HTTPS
            if (isHttps && externalHttpsProxy != null &&
                !(externalHttpsProxy.HostName == remoteHostName &&
                  externalHttpsProxy.Port == remotePort))
            {
                useHttpsProxy = true;

                //check if we need to ByPass
                if (externalHttpsProxy.BypassLocalhost &&
                    NetworkHelper.IsLocalIpAddress(remoteHostName))
                {
                    useHttpsProxy = false;
                }
            }

            TcpClient            client = null;
            CustomBufferedStream stream = null;

            try
            {
                if (isHttps)
                {
                    //If this proxy uses another external proxy then create a tunnel request for HTTPS connections
                    if (useHttpsProxy)
                    {
                        client = new TcpClient(server.UpStreamEndPoint);
                        await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port);

                        stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);

                        using (var writer = new StreamWriter(stream, Encoding.ASCII, server.BufferSize, true)
                        {
                            NewLine = ProxyConstants.NewLine
                        })
                        {
                            await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");

                            await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");

                            await writer.WriteLineAsync("Connection: Keep-Alive");

                            if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null)
                            {
                                await writer.WriteLineAsync("Proxy-Connection: keep-alive");

                                await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password)));
                            }
                            await writer.WriteLineAsync();

                            await writer.FlushAsync();

                            writer.Close();
                        }

                        using (var reader = new CustomBinaryReader(stream, server.BufferSize))
                        {
                            var result = await reader.ReadLineAsync();

                            if (!new[] { "200 OK", "connection established" }.Any(s => result.ContainsIgnoreCase(s)))
                            {
                                throw new Exception("Upstream proxy failed to create a secure tunnel");
                            }

                            await reader.ReadAndIgnoreAllLinesAsync();
                        }
                    }
                    else
                    {
                        client = new TcpClient(server.UpStreamEndPoint);
                        await client.ConnectAsync(remoteHostName, remotePort);

                        stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
                    }

                    var sslStream = new SslStream(stream, false, server.ValidateServerCertificate, server.SelectClientCertificate);
                    stream = new CustomBufferedStream(sslStream, server.BufferSize);

                    await sslStream.AuthenticateAsClientAsync(remoteHostName, null, server.SupportedSslProtocols, server.CheckCertificateRevocation);
                }
                else
                {
                    if (useHttpProxy)
                    {
                        client = new TcpClient(server.UpStreamEndPoint);
                        await client.ConnectAsync(externalHttpProxy.HostName, externalHttpProxy.Port);

                        stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
                    }
                    else
                    {
                        client = new TcpClient(server.UpStreamEndPoint);
                        await client.ConnectAsync(remoteHostName, remotePort);

                        stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
                    }
                }

                client.ReceiveTimeout = server.ConnectionTimeOutSeconds * 1000;
                client.SendTimeout    = server.ConnectionTimeOutSeconds * 1000;
            }
            catch (Exception)
            {
                stream?.Dispose();
                client?.Close();
                throw;
            }

            Interlocked.Increment(ref server.serverConnectionCount);

            return(new TcpConnection
            {
                UpStreamHttpProxy = externalHttpProxy,
                UpStreamHttpsProxy = externalHttpsProxy,
                HostName = remoteHostName,
                Port = remotePort,
                IsHttps = isHttps,
                TcpClient = client,
                StreamReader = new CustomBinaryReader(stream, server.BufferSize),
                Stream = stream,
                Version = httpVersion
            });
        }
        /// <summary>
        /// This is the core request handler method for a particular connection from client
        /// Will create new session (request/response) sequence until
        /// client/server abruptly terminates connection or by normal HTTP termination
        /// </summary>
        /// <param name="client"></param>
        /// <param name="clientStream"></param>
        /// <param name="clientStreamReader"></param>
        /// <param name="clientStreamWriter"></param>
        /// <param name="httpsConnectHostname"></param>
        /// <param name="endPoint"></param>
        /// <param name="connectRequest"></param>
        /// <param name="isTransparentEndPoint"></param>
        /// <returns></returns>
        private async Task HandleHttpSessionRequest(TcpClient client, CustomBufferedStream clientStream,
                                                    CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, string httpsConnectHostname,
                                                    ProxyEndPoint endPoint, ConnectRequest connectRequest, bool isTransparentEndPoint = false)
        {
            TcpConnection connection = null;

            try
            {
                //Loop through each subsequest request on this particular client connection
                //(assuming HTTP connection is kept alive by client)
                while (true)
                {
                    // read the request line
                    string httpCmd = await clientStreamReader.ReadLineAsync();

                    if (string.IsNullOrEmpty(httpCmd))
                    {
                        break;
                    }

                    var args = new SessionEventArgs(BufferSize, endPoint, ExceptionFunc)
                    {
                        ProxyClient = { TcpClient = client },
                        WebSession  = { ConnectRequest = connectRequest }
                    };

                    try
                    {
                        Request.ParseRequestLine(httpCmd, out string httpMethod, out string httpUrl, out var version);

                        //Read the request headers in to unique and non-unique header collections
                        await HeaderParser.ReadHeaders(clientStreamReader, args.WebSession.Request.Headers);

                        Uri httpRemoteUri;
                        if (uriSchemeRegex.IsMatch(httpUrl))
                        {
                            try
                            {
                                httpRemoteUri = new Uri(httpUrl);
                            }
                            catch (Exception ex)
                            {
                                throw new Exception($"Invalid URI: '{httpUrl}'", ex);
                            }
                        }
                        else
                        {
                            string host        = args.WebSession.Request.Host ?? httpsConnectHostname;
                            string hostAndPath = host;
                            if (httpUrl.StartsWith("/"))
                            {
                                hostAndPath += httpUrl;
                            }

                            string url = string.Concat(httpsConnectHostname == null ? "http://" : "https://", hostAndPath);
                            try
                            {
                                httpRemoteUri = new Uri(url);
                            }
                            catch (Exception ex)
                            {
                                throw new Exception($"Invalid URI: '{url}'", ex);
                            }
                        }

                        args.WebSession.Request.RequestUri  = httpRemoteUri;
                        args.WebSession.Request.OriginalUrl = httpUrl;

                        args.WebSession.Request.Method      = httpMethod;
                        args.WebSession.Request.HttpVersion = version;
                        args.ProxyClient.ClientStream       = clientStream;
                        args.ProxyClient.ClientStreamReader = clientStreamReader;
                        args.ProxyClient.ClientStreamWriter = clientStreamWriter;

                        //proxy authorization check
                        if (!args.IsTransparent && httpsConnectHostname == null && await CheckAuthorization(clientStreamWriter, args) == false)
                        {
                            break;
                        }

                        if (!isTransparentEndPoint)
                        {
                            PrepareRequestHeaders(args.WebSession.Request.Headers);
                            args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority;
                        }

                        //if win auth is enabled
                        //we need a cache of request body
                        //so that we can send it after authentication in WinAuthHandler.cs
                        if (isWindowsAuthenticationEnabledAndSupported && args.WebSession.Request.HasBody)
                        {
                            await args.GetRequestBody();
                        }

                        //If user requested interception do it
                        if (BeforeRequest != null)
                        {
                            await BeforeRequest.InvokeAsync(this, args, ExceptionFunc);
                        }

                        var response = args.WebSession.Response;

                        if (args.WebSession.Request.CancelRequest)
                        {
                            await HandleHttpSessionResponse(args);

                            if (!response.KeepAlive)
                            {
                                break;
                            }

                            continue;
                        }

                        //create a new connection if hostname/upstream end point changes
                        if (connection != null &&
                            (!connection.HostName.Equals(args.WebSession.Request.RequestUri.Host, StringComparison.OrdinalIgnoreCase) ||
                             (args.WebSession.UpStreamEndPoint != null &&
                              !args.WebSession.UpStreamEndPoint.Equals(connection.UpStreamEndPoint))))
                        {
                            connection.Dispose();
                            connection = null;
                        }

                        if (connection == null)
                        {
                            connection = await GetServerConnection(args, false);
                        }

                        //if upgrading to websocket then relay the requet without reading the contents
                        if (args.WebSession.Request.UpgradeToWebSocket)
                        {
                            //prepare the prefix content
                            var requestHeaders = args.WebSession.Request.Headers;
                            await connection.StreamWriter.WriteLineAsync(httpCmd);

                            await connection.StreamWriter.WriteHeadersAsync(requestHeaders);

                            string httpStatus = await connection.StreamReader.ReadLineAsync();

                            Response.ParseResponseLine(httpStatus, out var responseVersion, out int responseStatusCode, out string responseStatusDescription);
                            response.HttpVersion       = responseVersion;
                            response.StatusCode        = responseStatusCode;
                            response.StatusDescription = responseStatusDescription;

                            await HeaderParser.ReadHeaders(connection.StreamReader, response.Headers);

                            if (!args.IsTransparent)
                            {
                                await clientStreamWriter.WriteResponseAsync(response);
                            }

                            //If user requested call back then do it
                            if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked)
                            {
                                await BeforeResponse.InvokeAsync(this, args, ExceptionFunc);
                            }

                            await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize,
                                                    (buffer, offset, count) => { args.OnDataSent(buffer, offset, count); },
                                                    (buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); },
                                                    ExceptionFunc);

                            break;
                        }

                        //construct the web request that we are going to issue on behalf of the client.
                        await HandleHttpSessionRequestInternal(connection, args);

                        //if connection is closing exit
                        if (!response.KeepAlive)
                        {
                            break;
                        }
                    }
                    catch (Exception e) when(!(e is ProxyHttpException))
                    {
                        throw new ProxyHttpException("Error occured whilst handling session request", e, args);
                    }
                    finally
                    {
                        args.Dispose();
                    }
                }
            }
            finally
            {
                connection?.Dispose();
            }
        }