/*
* Copyright ( c ) 2014 , Oracle and / or its affiliates . All rights reserved .
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER .
*
* This code is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License version 2 only , as
* published by the Free Software Foundation .
*
* This code is distributed in the hope that it will be useful , but WITHOUT
* ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE . See the GNU General Public License
* version 2 for more details ( a copy is included in the LICENSE file that
* accompanied this code ) .
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work ; if not , write to the Free Software Foundation ,
* Inc . , 51 Franklin St , Fifth Floor , Boston , MA 02110 - 1301 USA .
*
* Please contact Oracle , 500 Oracle Parkway , Redwood Shores , CA 94065 USA
* or visit www . oracle . com if you need additional information or have any
* questions .
*/
import java.io.*;
import java.net.*;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
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.*;
/**
* @ test
* @ bug 8025710
* @ summary Proxied https connection reuse by HttpClient can send CONNECT to the server
* @ run main / othervm B8025710
*/
public class B8025710 {
private final static AtomicBoolean connectInServer = new AtomicBoolean();
private static final String keystorefile =
System.getProperty("test.src" , "./" )
+ "/../../../../../javax/net/ssl/etc/keystore" ;
private static final String passphrase = "passphrase" ;
public static void main(String[] args) throws Exception {
new B8025710().runTest();
if (connectInServer.get())
throw new RuntimeException("TEST FAILED: server got proxy header" );
else
System.out.println("TEST PASSED" );
}
private void runTest() throws Exception {
ProxyServer proxyServer = new ProxyServer();
HttpServer httpServer = new HttpServer();
httpServer.start();
proxyServer.start();
URL url = new URL("https" , InetAddress.getLocalHost().getHostName(),
httpServer.getPort(), "/" );
Proxy proxy = new Proxy(Proxy.Type.HTTP, proxyServer.getAddress());
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 ;
}
String response = readLine(connection.getInputStream());
if (!"Hi!" .equals(response)) {
System.err.println("Client: Unexpected response body: "
+ response);
}
}
httpServer.close();
proxyServer.close();
httpServer.join();
proxyServer.join();
}
class ProxyServer extends Thread implements Closeable {
private final ServerSocket proxySocket;
private final Pattern connectLinePattern =
Pattern.compile("^CONNECT ([^: ]+):([0-9]+) HTTP/[0-9.]+$" );
private final String PROXY_RESPONSE =
"HTTP/1.0 200 Connection Established\r\n"
+ "Proxy-Agent: TestProxy/1.0\r\n"
+ "\r\n" ;
ProxyServer() throws Exception {
super ("ProxyServer Thread" );
// Create the http proxy server socket
proxySocket = ServerSocketFactory.getDefault().createServerSocket();
proxySocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 0 ));
}
public SocketAddress getAddress() { return proxySocket.getLocalSocketAddress(); }
@Override
public void close() throws IOException {
proxySocket.close();
}
@Override
public void run() {
ArrayList<Thread > threads = new ArrayList<>();
int connectionCount = 0 ;
try {
while (connectionCount++ < 2 ) {
final Socket clientSocket = proxySocket.accept();
final int proxyConnectionCount = connectionCount;
System.out.println("Proxy: NEW CONNECTION "
+ proxyConnectionCount);
Thread t = new Thread ("ProxySocket" + proxyConnectionCount) {
@Override
public void run() {
try {
String firstLine =
readHeader(clientSocket.getInputStream());
Matcher connectLineMatcher =
connectLinePattern.matcher(firstLine);
if (!connectLineMatcher.matches()) {
System.out.println("Proxy: Unexpected"
+ " request to the proxy: "
+ firstLine);
return ;
}
String host = connectLineMatcher.group(1 );
String portStr = connectLineMatcher.group(2 );
int port = Integer.parseInt(portStr);
Socket serverSocket = SocketFactory.getDefault()
.createSocket(host, port);
clientSocket.getOutputStream()
.write(PROXY_RESPONSE.getBytes("UTF-8" ));
ProxyTunnel copyToClient =
new ProxyTunnel(serverSocket, clientSocket);
ProxyTunnel copyToServer =
new ProxyTunnel(clientSocket, serverSocket);
copyToClient.start();
copyToServer.start();
copyToClient.join();
// here copyToClient.close() would not provoke the
// bug ( since it would trigger the retry logic in
// HttpURLConnction.writeRequests ), so close only
// the output to get the connection in this state.
clientSocket.shutdownOutput();
try {
Thread .sleep(3000 );
} catch (InterruptedException ignored) { }
// now close all connections to finish the test
copyToServer.close();
copyToClient.close();
} catch (IOException | NumberFormatException
| InterruptedException e) {
e.printStackTrace();
}
}
};
threads.add(t);
t.start();
}
for (Thread t: threads)
t.join();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* This inner class provides unidirectional data flow through the sockets
* by continuously copying bytes from the input socket onto the output
* socket , until both sockets are open and EOF has not been received .
*/
class ProxyTunnel extends Thread {
private final Socket sockIn;
private final Socket sockOut;
private final InputStream input;
private final OutputStream output;
public ProxyTunnel(Socket sockIn, Socket sockOut) throws IOException {
super ("ProxyTunnel" );
this .sockIn = sockIn;
this .sockOut = sockOut;
input = sockIn.getInputStream();
output = sockOut.getOutputStream();
}
public void run() {
byte [] buf = new byte [8192 ];
int bytesRead;
try {
while ((bytesRead = input.read(buf)) >= 0 ) {
output.write(buf, 0 , bytesRead);
output.flush();
}
} catch (IOException ignored) {
close();
}
}
public void close() {
try {
if (!sockIn.isClosed())
sockIn.close();
if (!sockOut.isClosed())
sockOut.close();
} catch (IOException ignored) { }
}
}
/**
* the server thread
*/
class HttpServer extends Thread implements Closeable {
private final ServerSocket serverSocket;
private final SSLSocketFactory sslSocketFactory;
private final String serverResponse =
"HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/plain\r\n"
+ "Content-Length: 3\r\n"
+ "\r\n"
+ "Hi!" ;
private int connectionCount = 0 ;
HttpServer() throws Exception {
super ("HttpServer Thread" );
KeyStore ks = KeyStore.getInstance("JKS" );
ks.load(new FileInputStream(keystorefile), passphrase.toCharArray());
KeyManagerFactory factory = KeyManagerFactory.getInstance("SunX509" );
factory.init(ks, passphrase.toCharArray());
SSLContext ctx = SSLContext.getInstance("TLS" );
ctx.init(factory.getKeyManagers(), null , null );
sslSocketFactory = ctx.getSocketFactory();
// Create the server that the test wants to connect to via the proxy
serverSocket = ServerSocketFactory.getDefault().createServerSocket();
serverSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 0 ));
}
public int getPort() { return serverSocket.getLocalPort(); }
@Override
public void close() throws IOException { serverSocket.close(); }
@Override
public void run() {
try {
while (connectionCount++ < 2 ) {
Socket socket = serverSocket.accept();
System.out.println("Server: NEW CONNECTION "
+ connectionCount);
SSLSocket sslSocket = (SSLSocket) sslSocketFactory
.createSocket(socket,null , getPort(), false );
sslSocket.setUseClientMode(false );
sslSocket.startHandshake();
String firstLine = readHeader(sslSocket.getInputStream());
if (firstLine != null && firstLine.contains("CONNECT" )) {
System.out.println("Server: BUG! HTTP CONNECT"
+ " encountered: " + firstLine);
connectInServer.set(true );
}
// write the success response, the request body is not read.
// close only output and keep input open.
OutputStream out = sslSocket.getOutputStream();
out.write(serverResponse.getBytes("UTF-8" ));
socket.shutdownOutput();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* read the header and return only the first line .
*
* @ param inputStream the stream to read from
* @ return the first line of the stream
* @ throws IOException if reading failed
*/
private static String readHeader(InputStream inputStream)
throws IOException {
String line;
String firstLine = null ;
while ((line = readLine(inputStream)) != null && line.length() > 0 ) {
if (firstLine == null ) {
firstLine = line;
}
}
return firstLine;
}
/**
* read a line from stream .
*
* @ param inputStream the stream to read from
* @ return the line
* @ throws IOException if reading failed
*/
private static String readLine(InputStream inputStream)
throws IOException {
final StringBuilder line = new StringBuilder();
int ch;
while ((ch = inputStream.read()) != -1 ) {
if (ch == '\r' ) {
continue ;
}
if (ch == '\n' ) {
break ;
}
line.append((char ) ch);
}
return line.toString();
}
private SSLSocketFactory createTestSSLSocketFactory() {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession sslSession) {
// ignore the cert's CN; it's not important to this test
return true ;
}
});
// 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 X509Certificate[] getAcceptedIssuers() {
return null ;
}
@Override
public void checkClientTrusted(X509Certificate[] certs,
String authType) {
}
@Override
public void checkServerTrusted(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();
}
}
Messung V0.5 in Prozent C=88 H=91 G=89
¤ Dauer der Verarbeitung: 0.9 Sekunden
(vorverarbeitet am 2026-06-10)
¤
*© Formatika GbR, Deutschland