JDK-8025710 : Proxied HTTPS connections reused by HttpClient can send CONNECT to the server
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.net
  • Affected Version: 6u34,7u21,7u40,8,9
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: linux
  • Submitted: 2013-09-26
  • Updated: 2016-05-27
  • Resolved: 2014-04-23
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 7 JDK 8 JDK 9
7u76Fixed 8u20Fixed 9 b11Fixed
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.7.0_40"
Java(TM) SE Runtime Environment (build 1.7.0_40-b43)
Java HotSpot(TM) 64-Bit Server VM (build 24.0-b56, mixed mode)

This bug also exists in 1.6.0_34 and 1.7.0_21:

java version "1.6.0_34"
Java(TM) SE Runtime Environment (build 1.6.0_34-b04)
Java HotSpot(TM) 64-Bit Server VM (build 20.9-b04, mixed mode)

java version "1.7.0_21"
Java(TM) SE Runtime Environment (build 1.7.0_21-b11)
Java HotSpot(TM) 64-Bit Server VM (build 23.21-b01, mixed mode)


ADDITIONAL OS VERSION INFORMATION :
Linux slawrance-wsl 2.6.32-45-generic #104-Ubuntu SMP Tue Feb 19 21:20:09 UTC 2013 x86_64 GNU/Linux
This also happens in RedHat Enterprise Linux 4 and 5, at least

EXTRA RELEVANT SYSTEM CONFIGURATION :
This bug occurs in an environment where a HTTP proxy server is used for outbound connections.

A DESCRIPTION OF THE PROBLEM :
When a proxied https request is reused in the JDK's built-in HttpsURLConnection, a closed connection will cause the retry code to inadvertently send the 'CONNECT' request meant for the proxy to the remote endpoint via the new proxied connection.

  From a high level, the second request in a reused request where the remote endpoint had closed down its output stream causes an error handler in sun.net.www.http.HttpClient to try it again within a new proxy connection. It does establish a new proxied connection, but it ends up sending the wrong request through the established proxied connection. Instead of sending the intended request, it sends the http request that was used to establish the second connection to the proxy.

That bug in the error handler in HttpsURLConnection is very subtle. It appears that a part of it was coded it in a way that was intended to mitigate this issue, but it unfortunately wasn't sufficient.

In sun.net.www.http.HttpClient.parseHTTPHeader:765 (in 1.7.0u40's source code), the call to httpuc.doTunneling() backs up the 'requests' field and then restores it by the end of that method, but that's just in httpuc. When httpud.doTunneling() writes the 'CONNECT' request, it ends up calling the sun.net.www.http.HttpClient's writeRequests:598 method, which sets the 'requests' field of sun.net.www.http.HttpClient to the 'CONNECT' request's values. After httpuc.doTunneling() returns, it proceeds to retry the request with the newly established proxy connection, but it's unfortunately using the 'requests' field that was overwritten by the writeRequests() call from the 'CONNECT' request. Consequently, the remote endpoint sees a 'CONNECT' request instead of the intended request.

Fortunately, it is possible to override sun.net.www.http.HttpClient by using the endorsed jar mechanism. I tried that out and was able to verify that it successfully fixes the issue. The patch that fixed this issue is the following:

--- /home/slawrance/dev/tools/Linux/jdk/jdk1.7.0_40_x64/src/jdk/src/share/classes/sun/net/www/http/HttpClient.java      2013-09-06 11:28:38.000000000 -0700
+++ HttpClient.java     2013-09-25 23:14:19.693524881 -0700
@@ -651,7 +634,9 @@
                     // try once more
                     openServer();
                     if (needsTunneling()) {
+                        final MessageHeader origRequests = requests;
                         httpuc.doTunneling();
+                        requests = origRequests;
                     }
                     afterConnect();
                     writeRequests(requests, poster);
@@ -762,7 +747,9 @@
                         cachedHttpClient = false;
                         openServer();
                         if (needsTunneling()) {
+                            final MessageHeader origRequests = requests;
                             httpuc.doTunneling();
+                            requests = origRequests;
                         }
                         afterConnect();
                         writeRequests(requests, poster);



STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the test case code. The Test.java class starts up a https server and a proxy server so that it can make two requests via the proxy to the https server in a manner that triggers reuse of the first connection.

I was able to verify that this test fails without the fix and passes with the fix. Details exist in the expected and actual results below.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
After the fix is applied via the endorsed jar mechanism, the test passes:

$ ~/dev/tools/Linux/jdk/jdk1.7.0_40_x64/bin/java -Djava.endorsed.dirs=/home/slawrance/dev/system/ext/sun_misc/HttpClientFix/jars Test
Client: Requesting https://slawrance-wsl.internal.salesforce.com:7517/ via HTTP @ slawrance-wsl.internal.salesforce.com/10.0.63.231:47856 (attempt 1 of 2)
Proxy: NEW CONNECTION
Server: NEW CONNECTION
Client: Requesting https://slawrance-wsl.internal.salesforce.com:7517/ via HTTP @ slawrance-wsl.internal.salesforce.com/10.0.63.231:47856 (attempt 2 of 2)
Proxy: NEW CONNECTION
Server: NEW CONNECTION
TEST PASSED

ACTUAL -
Failure (current) output:

$ ~/dev/tools/Linux/jdk/jdk1.7.0_40_x64/bin/java Test
Client: Requesting https://slawrance-wsl.internal.salesforce.com:10318/ via HTTP @ slawrance-wsl.internal.salesforce.com/10.0.63.231:5445 (attempt 1 of 2)
Proxy: NEW CONNECTION
Server: NEW CONNECTION
Client: Requesting https://slawrance-wsl.internal.salesforce.com:10318/ via HTTP @ slawrance-wsl.internal.salesforce.com/10.0.63.231:5445 (attempt 2 of 2)
Proxy: NEW CONNECTION
Server: NEW CONNECTION
Server: BUG! HTTP CONNECT encountered: CONNECT slawrance-wsl.internal.salesforce.com:10318 HTTP/1.1
TEST FAILED

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.io.*;
import java.net.*;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.*;

import com.sun.org.apache.xml.internal.security.exceptions.Base64DecodingException;
import com.sun.org.apache.xml.internal.security.utils.Base64;

/**
 * Test case for the bug where a closed connection will cause the retry code to inadvertently send the 'CONNECT' request
 * meant for the proxy to the remote endpoint via the new proxied connection when a proxied https request is reused in
 * the JDK's built-in HttpsURLConnection.
 *
 * @author Steven Lawrance
 */
public class Test {
    private final InetSocketAddress serverSocketAddress, proxySocketAddress;
    private final Thread serverThread, proxyThread;
    private final AtomicBoolean keepRunning = new AtomicBoolean(true);
    private final AtomicBoolean connectObservedInServer = new AtomicBoolean();

    /** Key and certificate for the https server in this test, expressed as base64 */
    private final String TEST_PKCS12_FILE = "MIIKZgIBAzCCCjAGCSqGSIb3DQEHAaCCCiEEggodMIIKGTCCBJ8GCSqGSIb3DQEHBqCCBJAwggSM"
            + "AgEAMIIEhQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIwqUBH+SDbMYCAggAgIIEWFyrRsMm"
            + "PxJ7mqJ6EEK63TLrcjfpbc+jEK3w5nWB8NfuILFy1fvCX3Z5VfOrU0SV+ZSrbFzXL52XKItwq7nD"
            + "WcDPAoBybKQZcOjTkTPKUlIQJdU+oovOS69ER52wOAu3FySEYCXVjVO4xQ9Jr9fmx0wbScI6ramT"
            + "+oOJs9woi12/NQ2zLj0cIk1x84kD4Lw/vMe/2q6D6XjxZWrWCJaZNo4VJwssLBuSquM6VuW3q63r"
            + "L8NedZ4BId6CyR0Ft/4JVQM+kpMDshhj5wnUUfPDjvldhHP1c1MSiI/gNxwbn/Ielh1zMjffitIZ"
            + "WY2UUmVFlpg3CElA1gJKylQnXvSuXu1jw/Vnd4raI32tOqlovEx2/Iey/W0ZBj8vXzKqcYW7sCzf"
            + "i2qEyWYvSLyqCVdOMc6wkbK5a3QInamV+2fcl3z3/l//4/yzZ6xhNQ7+9LlZs7YuMreR6og+dfWK"
            + "FsIaZDz1sr1bHdAEJNE5YcaJkRqkT74eyKMalRa9HBHZXCG5y06JgwO6h7P4MtOltIPi/HHhHvs5"
            + "VXZsLljLj/JyinDNNKPvhw3SxF1r4XMF2sNN+6eSGuWNjmWp1mXP/Pio8vgQH/epWlhee3b6WYYu"
            + "bpSvHHRBdYqfdnUd4SKvgi074SFxJ7r/8XykAr7lpxqjuxPMExcEh/BsmNaXBAB3uv5B3E83tU49"
            + "nQ+kas+QNdx08D8EOLKYhUGOsa3iMMtrmlD643Zd031uBB5JLcBRK6ZgBg16I8pBv0PjWRe3Bg7X"
            + "rOemfUZySfsDVRIiZqvy0DbkvqvhiAMOIkWmQW6gfV/1/KeSm4NV12MCRL4B68xPn+t1Ok3O0TG2"
            + "HkSmGUTwuLkv9dn4dW2rz1fr+9LJk27nr9+Ecn4hUfDx8xIp9rsqd3wJSqSWC55uafCq3zC5CvOY"
            + "7DoLehM8w3GiAscZupIhG34HIt9NbQcN1RQus6TgkNWMh3s8fGM7ypWCmptN8V1wTMb1E+9zwlFg"
            + "NQHhqg0hGwSwaF5R7+w1+QzP38Xiss5Es+Qxi2DPjhNNzHTb33JKo6VoPHUgAdHAT2VtHjhnIb6G"
            + "PRhKI0NxrSrsfZZY2x1JnwfH0UhbDGJheObeGLplzZYvHcUw+dDRS+nPvt8faCos3Mmdz9Ux4FiX"
            + "trXvp/3PadFduC/mKBC7/aAyu1HW0dCJd0djZbn0+VzOfr8TRPI5j1/uaELzSF67PlKr1L/PVFlL"
            + "GdlwhMLDLPthUt3Wu7aHOOxkRZuXsHCveSIh+y4VsWH2u4SQEYEd8xASf6D/p4MtC1JIe+vapoEN"
            + "7uLdIuG75Td2Qh12/VDbCAHFaRFqeDpdlz7qG5hrQbrSnTuGFxVEJM9tMYxshDeK0+IJ44cSQBNi"
            + "FD3Cko281Pcgoeiqp3a43q7o12reaXU5smHLJ5BgCz1jrbBVagmX4WYIOy9iq2QjWa0Fh7aHIfAi"
            + "Vf8BB4aeGBUUpFyNzOOHaSBfWtFWF75TMIIFcgYJKoZIhvcNAQcBoIIFYwSCBV8wggVbMIIFVwYL"
            + "KoZIhvcNAQwKAQKgggTuMIIE6jAcBgoqhkiG9w0BDAEDMA4ECFiQCKVgBtWJAgIIAASCBMi1w33Q"
            + "cFzrSVWzSo8fBb74biRDlMAhVFUYyQt3uU7i9ss7/Eqetvg+2erZXx4ywhlwSy7p3lQ5+rJvRYPk"
            + "ayxzoMLYP3Q102OHtGANwhRGFbX+PKEyB9o6qBj1CfcuEptkpCvdxYbnZ8AThzyBDwWEyVJX81Zw"
            + "0r8GpuTg6sFtfdd1Ytjzxr2MpJsrqukFD5D9/8YCHYNab3A8JrkudvHNUUlgmHH4hmEEMOxnSlc6"
            + "tIGoWqSi3KhCZjVDiZB6dzUih2TwDkklfwa5r65/lpp9xgBKK1wf6Q3cbhwrncn9kNbnBBH/nQxO"
            + "qsvFRzypBnU7u9XmRdBIZox9yM8Vgt+Elf90E44xiMF+RV8mzGOvogQ2QUpLsZ8JCcmMq7z3Rpth"
            + "HDmHl067VhAll7qAZcUqtEvgutLx6QX/UmFBumdWjrNqn4S3EpaiZhe4qQKlaIHZJvnvrGpAwxGt"
            + "0VRuDFH9B98hx7k7Rfuw4nz/AcQf3v51dY950SoAIHtbk6xQ11Ad0YJ0lUsdck4uhMpvrM4OjtG4"
            + "UEv4ywo6JVsjXQ9bo2j6asR4NnGR5Gs7PkyEn6Yz4DTjj/hW9m8uqUjcNbxPlASx+owNsEkL9IqW"
            + "XV7KRcPQFd1dsWttMnBh9yfr62yGBCzEj7FNcDNOpzjZFUbbe8X55GHeBQN82BHZglA1A6rD6a1H"
            + "QXUHkXcPrh8xpRsh5MRKMf0LgV5bd2XnwQgbeq3EFbyYdx+uz7RwxNOhp5OHHasSMjAbLNNBRIUH"
            + "YGoWH6NxQIZ7WiGpno9ZnfBLY32b9MQKJMRRiv93CGtjo2wtKZyEeVvJI99UBBz2+ateeNknG054"
            + "oi1qENiDc8iU0lYyTP4acNKjcso0m5X4X1PJLNlj6DOzmG8x2XeESXM2FtHLpqWtbtGhRsJPsPJE"
            + "KqWpUZ1l148i7XbJkixLhgUdpJhT69Bubcc7JkdOKbpvIW3ipPXS9GbSwhJVN95nRot8sEjM8uqe"
            + "6kJ1/VV250Cw2ZJj2BDhK/Teq9GKm27YbC7YWUSo5YOQSfaL2k3zD5ejDTMCiWjK2FDaFQ4fHvBw"
            + "UQyYeX3IdPo5HdwTcfrGzVCYMUxKN3eUbcYMZajC25mkd7IvPNDvezUyb0KmAAip+xnXfb0Z9zGF"
            + "c1lzv7MkwPFI0ceYwCX49d4NDGMZ67rG+zhYR0UgxnzyMav5u4c+9gz9PZLdZRqooNpusY3A21v5"
            + "iXRY32u7UcY2QYMR4tquJ3lKKFY9OWaOnC2cLK/fuK+3IEBdK6ZejXXmS7a5g6HCHBEFb+wOjOiT"
            + "P795XuB6d9m2tqED4WojxnP+shagD9DU7cRcDpwYiEqhIh44OE5m9vpoX1IIk7Y89QES9tcfmEFS"
            + "EEF1FblMZC0LjYOEuqvStRxFOoa0SuD1vEUnJdGQXrRAlvOrQK+Hx128wSO5cC895D7/MKmRenRp"
            + "sIjuKG9nFHnzagQ5kR9Q4II5Y73qCf9894KMCp5MdK/IEZoY/yhWKHgwE9iDL53URstRoqD2OfqO"
            + "sjxKYlOsC52IoL9xlqFVm2ahsaP+9lOzrgNo43D8GMNuGRuZ9UEJAKoi7QTknt2vEktnA5gj0TyT"
            + "7aZYdO2sAVFRydlRsT8oiOhYhZoXMrExVjAjBgkqhkiG9w0BCRUxFgQUr8hoZhCTn3Jpm5tZ1GOM"
            + "396ULBkwLwYJKoZIhvcNAQkUMSIeIABUAGUAcwB0ACAAQwBlAHIAdABpAGYAaQBjAGEAdABlMC0w"
            + "ITAJBgUrDgMCGgUABBQb3xdMv5tvFY0Qi7geGD1SBoG5JwQIAcFtrXRzzmA=";

    public static void main(String[] args) {
        try {
            final Test test = new Test();
            test.runTest();
            test.keepRunning.set(false);
            test.serverThread.interrupt();
            test.serverThread.join();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.exit(0);
    }

    private void runTest() throws IOException {
        final URL url = new URL("https", serverSocketAddress.getHostName(), serverSocketAddress.getPort(), "/");
        final Proxy proxy = new Proxy(Proxy.Type.HTTP, proxySocketAddress);
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession sslSession) {
                return serverSocketAddress.getHostName().equals(hostname); // ignore the cert's CN; it's not important to this test
            }
        });
        HttpsURLConnection.setDefaultSSLSocketFactory(createTestSSLSocketFactory());

        // Make two connections. The bug occurs when the second request is made
        for (int i = 0; i < 2; i++) {
            System.out.println("Client: Requesting " + url.toExternalForm() + " via " + proxy.toString() + " (attempt " + (i + 1) + " of 2)");
            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(proxy);
            connection.setRequestMethod("POST");
            connection.setDoInput(true);
            connection.setDoOutput(true);
            connection.setRequestProperty("User-Agent", "Test/1.0");
            connection.getOutputStream().write("Hello, world!".getBytes("UTF-8"));
            if (connection.getResponseCode() != 200) {
                System.err.println("Client: Unexpected response code " + connection.getResponseCode());
                break;
            }
            final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
            final String response = reader.readLine();
            if (!"Hi!".equals(response)) {
                System.err.println("Client: Unexpected response body: " + response);
            }
        }
        if (connectObservedInServer.get()) {
            System.err.println("TEST FAILED");
        } else {
            System.out.println("TEST PASSED");
        }
    }

    private Test() throws IOException, NoSuchAlgorithmException, KeyStoreException, CertificateException, KeyManagementException, UnrecoverableKeyException {
        KeyStore ks = openKeyStore(TEST_PKCS12_FILE, "testing123", "PKCS12");
        KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        factory.init(ks, "testing123".toCharArray());
        SSLContext ctx = SSLContext.getInstance("TLS");
        ctx.init(factory.getKeyManagers(), null, null);
        final SSLSocketFactory sslSocketFactory = ctx.getSocketFactory();

        // Create the server that the test wants to connect to via the proxy
        final ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket();
        serverSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 0 /* ephemeral port */));
        serverSocketAddress = (InetSocketAddress) serverSocket.getLocalSocketAddress();
        serverThread = new Thread("ServerThread") {
            @Override
            public void run() {
                try {
                    while (keepRunning.get()) {
                        final Socket socket = serverSocket.accept();
                        System.out.println("Server: NEW CONNECTION");
                        if (socket != null) {
                            try {
                                final SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket, null, serverSocketAddress.getPort(), false);
                                sslSocket.setUseClientMode(false);
                                sslSocket.startHandshake();
                                final BufferedReader reader = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
                                String line;
                                while (!sslSocket.isInputShutdown()) {
                                    try {
                                        String firstLine = null;
                                        while ((line = reader.readLine()) != null && line.length() > 0) {
                                            if (firstLine == null) {
                                                firstLine = line;
                                            }
                                        }
                                        if (line == null) {
                                            break;
                                        }
                                        if (firstLine != null && firstLine.contains("CONNECT")) {
                                            System.out.println("Server: BUG! HTTP CONNECT encountered: " + firstLine);
                                            connectObservedInServer.set(true);
                                        }
                                        final String response = "HTTP/1.1 200 OK\r
Content-Type: text/plain\r
Content-Length: 3\r
\r
Hi!";
                                        final OutputStream out = sslSocket.getOutputStream();
                                        out.write(response.getBytes("UTF-8"));
                                        out.flush();

                                        // Close the underlying connection's output writer after a second to ensure that the client and proxy
                                        // think that this connection is still open in both directions for the second request.
                                        try {
                                            Thread.sleep(1000);
                                        } catch (InterruptedException e) {
                                        }
                                        socket.shutdownOutput();
                                        break;
                                    } catch (IOException e) {
                                        e.printStackTrace();
                                        break;
                                    }
                                }
                            } catch (Exception e) {
                                try {
                                    socket.close();
                                } catch (IOException e2) {
                                }
                            }
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
        serverThread.start();

        // Create the http proxy server
        final ServerSocket proxySocket = ServerSocketFactory.getDefault().createServerSocket();
        proxySocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 0 /* ephemeral port */));
        proxySocketAddress = (InetSocketAddress) proxySocket.getLocalSocketAddress();
        proxyThread = new Thread("ProxyThread") {
            @Override
            public void run() {
                try {
                    final Pattern connectLinePattern = Pattern.compile("^CONNECT ([^: ]+):([0-9]+) HTTP/[0-9.]+$");
                    int proxyConnectionCount = 0;
                    while (keepRunning.get()) {
                        final Socket clientSocket = proxySocket.accept();
                        System.out.println("Proxy: NEW CONNECTION");
                        final int proxyThreadCount = ++proxyConnectionCount;
                        new Thread("ProxySocket" + proxyThreadCount) {
                            @Override
                            public void run() {
                                if (clientSocket != null) {
                                    try {
                                        final InputStream clientIn = clientSocket.getInputStream();
                                        String line;
                                        try {
                                            String firstLine = null;
                                            while ((line = readLineFromInputStream(clientIn)) != null && line.length() > 0) {
                                                if (firstLine == null) {
                                                    firstLine = line;
                                                }
                                            }
                                            if (line == null || firstLine == null) {
                                                return;
                                            }
                                            final Matcher connectLineMatcher = connectLinePattern.matcher(firstLine);
                                            if (!connectLineMatcher.matches()) {
                                                System.out.println("Proxy: Unexpected request to the proxy: " + firstLine);
                                                return;
                                            }
                                            final String host = connectLineMatcher.group(1);
                                            final String portStr = connectLineMatcher.group(2);
                                            final int port = Integer.parseInt(portStr);
                                            final Socket serverSocket = SocketFactory.getDefault().createSocket(host, port);
                                            final InputStream serverIn = serverSocket.getInputStream();
                                            final OutputStream serverOut = serverSocket.getOutputStream();
                                            final OutputStream clientOut = clientSocket.getOutputStream();
                                            clientOut.write("HTTP/1.0 200 Connection Established\r
Proxy-Agent: TestProxy/1.0\r
\r
".getBytes("UTF-8"));
                                            final Thread toServerCopier = new Thread("ProxyCopierToServer" + proxyThreadCount) {
                                                @Override
                                                public void run() {
                                                    try {
                                                        final byte[] b = new byte[1024];
                                                        int byteCount;
                                                        while ((byteCount = clientIn.read(b)) > 0) {
                                                            serverOut.write(b, 0, byteCount);
                                                        }
                                                    } catch (IOException e) {
                                                        // Socket is probably closed
                                                    } catch (Exception e) {
                                                        e.printStackTrace();
                                                    }
                                                    try {
                                                        serverSocket.shutdownOutput();
                                                    } catch (IOException e) {
                                                        // Socket is probably closed
                                                    } catch (Exception e) {
                                                        e.printStackTrace();
                                                    }
                                                }
                                            };
                                            toServerCopier.start();
                                            try {
                                                final byte[] b = new byte[1024];
                                                int byteCount;
                                                while ((byteCount = serverIn.read(b)) > 0) {
                                                    clientOut.write(b, 0, byteCount);
                                                }
                                            } catch (IOException e) {
                                                // Socket is probably closed
                                            }
                                            try {
                                                clientSocket.shutdownOutput();
                                            } catch (IOException e) {
                                                // Socket is probably closed
                                            } catch (Exception e) {
                                                e.printStackTrace();
                                            }
                                            toServerCopier.join();
                                        } catch (IOException e) {
                                            e.printStackTrace();
                                        }
                                    } catch (Exception e) {
                                        try {
                                            e.printStackTrace();
                                            clientSocket.close();
                                        } catch (IOException e2) {
                                        }
                                    }
                                }
                            }
                        }.start();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
        proxyThread.start();
    }

    private String readLineFromInputStream(InputStream in) throws IOException {
        final StringBuilder s = new StringBuilder();
        int ch;
        while ((ch = in.read()) >= 0) {
            if (ch == '\r') {
                continue;
            }
            if (ch == '
') {
                break;
            }
            s.append((char) ch);
        }
        return s.toString();
    }

    private KeyStore openKeyStore(String pkcs12FileBase64, String password, String type) throws IOException, NoSuchAlgorithmException, KeyStoreException, CertificateException {
        ByteArrayInputStream fin = null;
        char[] passphrase = password.toCharArray();
        KeyStore ks = KeyStore.getInstance(type);
        try {
            fin = new ByteArrayInputStream(Base64.decode(pkcs12FileBase64));
        } catch (Base64DecodingException e) {
            throw new RuntimeException(e);
        }
        ks.load(fin, passphrase);
        return ks;
    }

    private SSLSocketFactory createTestSSLSocketFactory() {

        // Set up the socket factory to use a trust manager that trusts all certs, since trust validation isn't important to this test
        final TrustManager[] trustAllCertChains = new TrustManager[] { new X509TrustManager() {
            @Override
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return null;
            }

            @Override
            public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {}

            @Override
            public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
        } };
        final SSLContext sc;
        try {
            sc = SSLContext.getInstance("TLS");
        }
        catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        try {
            sc.init(null, trustAllCertChains, new java.security.SecureRandom());
        }
        catch (KeyManagementException e) {
            throw new RuntimeException(e);
        }
        return sc.getSocketFactory();
    }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
As a workaround, it is possible to set "Connection: close" in the HttpsURLConnection request. By turning off the use of persistent connections, this bug is worked around.