private static async Task HandleForwarderAsync(TcpClientWrapper client, TcpClientWrapper forwarder, CancellationToken token) { while (!token.IsCancellationRequested && forwarder.IsConnected) { var data = await forwarder.Read(); if (data == null) { break; } // Write the raw bytes received from the forwarder to the client. client.Write(data.RawBytes); // Important Note: // Attempting to use encoding to convert the data to a string will seemingly // result in some data being lost as the proxy no longer function. } }
public async Task HandleClientAsync(TcpClientWrapper client, CancellationToken token) { var forwarder = new TcpClientWrapper(); while (!token.IsCancellationRequested && client.IsConnected) { var data = await client.Read(); if (data == null) { break; } // If the forwarder has a connection to a server // forward the data from to the server. if (forwarder.IsConnected) { forwarder.Write(data.RawBytes); continue; } // If the forwarder is not connected, we are receiving headers. // TODO: Ensure that all the header data has been received by constantly reading until encountering "\r\n\r\n" - the header terminator. // TODO: This also means that the buffer size can be reduced etc. // Otherwise, parse the headers from the client. var headers = HttpHeader.Parse(data.ToString()); // Extract the remote address. var hasHostField = headers.Fields.ContainsKey("Host"); var hostArray = hasHostField ? headers.Fields["Host"].Split(':') : ParseUri(headers.RequestLine.Uri); var host = hostArray[0]; var port = Convert.ToInt32(hostArray.Length == 1 ? "80" : hostArray[1]); // Output the request received. Console.WriteLine($"Received {headers.RequestLine.Method} request for {host}:{port}."); // Try connect to the server. var connectionResponse = await forwarder.Connect(host, port); // If the forwarder fails to connect to the server. // Send a bad gateway response and break out of the loop. if (!connectionResponse.Success) { var exceptionMessage = connectionResponse.Message; client.WriteHeaders("HTTP/1.1 502 Bad Gateway", $"Content-Length: {exceptionMessage.Length}", "Connection: close"); client.WriteLine(exceptionMessage); break; } // If the method of the request is a connect request. // Send a connection established response to the client. if (headers.RequestLine.Method == "CONNECT") { client.WriteHeaders("HTTP/1.1 200 Connection Established"); // TODO: We may need to check if the port is 443 here. As it could be possible that the connection doesn't involve SSL. if (_decryptHttpsTraffic) { await DecryptHttpsTrafficAsync(client, forwarder, host); } } // Otherwise, forward the data sent by the client to the server. else { forwarder.Write(data.RawBytes); } // Start handling the forwarder. var task = HandleForwarderAsync(client, forwarder, token) .ContinueWith(precursorTask => forwarder.Close(), token); } }