Path: blob/master/test/jdk/sun/net/www/http/HttpClient/B8025710.java
41154 views
/*1* Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223import java.io.*;24import java.net.*;25import java.security.*;26import java.security.cert.X509Certificate;27import java.util.ArrayList;28import java.util.concurrent.atomic.AtomicBoolean;29import java.util.regex.Matcher;30import java.util.regex.Pattern;31import javax.net.ServerSocketFactory;32import javax.net.SocketFactory;33import javax.net.ssl.*;3435/**36* @test37* @bug 802571038* @summary Proxied https connection reuse by HttpClient can send CONNECT to the server39* @run main/othervm B802571040*/41public class B8025710 {4243private final static AtomicBoolean connectInServer = new AtomicBoolean();44private static final String keystorefile =45System.getProperty("test.src", "./")46+ "/../../../../../javax/net/ssl/etc/keystore";47private static final String passphrase = "passphrase";4849public static void main(String[] args) throws Exception {50new B8025710().runTest();5152if (connectInServer.get())53throw new RuntimeException("TEST FAILED: server got proxy header");54else55System.out.println("TEST PASSED");56}5758private void runTest() throws Exception {59ProxyServer proxyServer = new ProxyServer();60HttpServer httpServer = new HttpServer();61httpServer.start();62proxyServer.start();6364URL url = new URL("https", InetAddress.getLocalHost().getHostName(),65httpServer.getPort(), "/");6667Proxy proxy = new Proxy(Proxy.Type.HTTP, proxyServer.getAddress());6869HttpsURLConnection.setDefaultSSLSocketFactory(createTestSSLSocketFactory());7071// Make two connections. The bug occurs when the second request is made72for (int i = 0; i < 2; i++) {73System.out.println("Client: Requesting " + url.toExternalForm()74+ " via " + proxy.toString()75+ " (attempt " + (i + 1) + " of 2)");7677HttpsURLConnection connection =78(HttpsURLConnection) url.openConnection(proxy);7980connection.setRequestMethod("POST");81connection.setDoInput(true);82connection.setDoOutput(true);83connection.setRequestProperty("User-Agent", "Test/1.0");84connection.getOutputStream().write("Hello, world!".getBytes("UTF-8"));8586if (connection.getResponseCode() != 200) {87System.err.println("Client: Unexpected response code "88+ connection.getResponseCode());89break;90}9192String response = readLine(connection.getInputStream());93if (!"Hi!".equals(response)) {94System.err.println("Client: Unexpected response body: "95+ response);96}97}98httpServer.close();99proxyServer.close();100httpServer.join();101proxyServer.join();102}103104class ProxyServer extends Thread implements Closeable {105106private final ServerSocket proxySocket;107private final Pattern connectLinePattern =108Pattern.compile("^CONNECT ([^: ]+):([0-9]+) HTTP/[0-9.]+$");109private final String PROXY_RESPONSE =110"HTTP/1.0 200 Connection Established\r\n"111+ "Proxy-Agent: TestProxy/1.0\r\n"112+ "\r\n";113114ProxyServer() throws Exception {115super("ProxyServer Thread");116117// Create the http proxy server socket118proxySocket = ServerSocketFactory.getDefault().createServerSocket();119proxySocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 0));120}121122public SocketAddress getAddress() { return proxySocket.getLocalSocketAddress(); }123124@Override125public void close() throws IOException {126proxySocket.close();127}128129@Override130public void run() {131ArrayList<Thread> threads = new ArrayList<>();132int connectionCount = 0;133try {134while (connectionCount++ < 2) {135final Socket clientSocket = proxySocket.accept();136final int proxyConnectionCount = connectionCount;137System.out.println("Proxy: NEW CONNECTION "138+ proxyConnectionCount);139140Thread t = new Thread("ProxySocket" + proxyConnectionCount) {141@Override142public void run() {143try {144String firstLine =145readHeader(clientSocket.getInputStream());146147Matcher connectLineMatcher =148connectLinePattern.matcher(firstLine);149if (!connectLineMatcher.matches()) {150System.out.println("Proxy: Unexpected"151+ " request to the proxy: "152+ firstLine);153return;154}155156String host = connectLineMatcher.group(1);157String portStr = connectLineMatcher.group(2);158int port = Integer.parseInt(portStr);159160Socket serverSocket = SocketFactory.getDefault()161.createSocket(host, port);162163clientSocket.getOutputStream()164.write(PROXY_RESPONSE.getBytes("UTF-8"));165166ProxyTunnel copyToClient =167new ProxyTunnel(serverSocket, clientSocket);168ProxyTunnel copyToServer =169new ProxyTunnel(clientSocket, serverSocket);170171copyToClient.start();172copyToServer.start();173174copyToClient.join();175// here copyToClient.close() would not provoke the176// bug ( since it would trigger the retry logic in177// HttpURLConnction.writeRequests ), so close only178// the output to get the connection in this state.179clientSocket.shutdownOutput();180181try {182Thread.sleep(3000);183} catch (InterruptedException ignored) { }184185// now close all connections to finish the test186copyToServer.close();187copyToClient.close();188} catch (IOException | NumberFormatException189| InterruptedException e) {190e.printStackTrace();191}192}193};194threads.add(t);195t.start();196}197for (Thread t: threads)198t.join();199} catch (IOException | InterruptedException e) {200e.printStackTrace();201}202}203}204205/**206* This inner class provides unidirectional data flow through the sockets207* by continuously copying bytes from the input socket onto the output208* socket, until both sockets are open and EOF has not been received.209*/210class ProxyTunnel extends Thread {211private final Socket sockIn;212private final Socket sockOut;213private final InputStream input;214private final OutputStream output;215216public ProxyTunnel(Socket sockIn, Socket sockOut) throws IOException {217super("ProxyTunnel");218this.sockIn = sockIn;219this.sockOut = sockOut;220input = sockIn.getInputStream();221output = sockOut.getOutputStream();222}223224public void run() {225byte[] buf = new byte[8192];226int bytesRead;227228try {229while ((bytesRead = input.read(buf)) >= 0) {230output.write(buf, 0, bytesRead);231output.flush();232}233} catch (IOException ignored) {234close();235}236}237238public void close() {239try {240if (!sockIn.isClosed())241sockIn.close();242if (!sockOut.isClosed())243sockOut.close();244} catch (IOException ignored) { }245}246}247248/**249* the server thread250*/251class HttpServer extends Thread implements Closeable {252253private final ServerSocket serverSocket;254private final SSLSocketFactory sslSocketFactory;255private final String serverResponse =256"HTTP/1.1 200 OK\r\n"257+ "Content-Type: text/plain\r\n"258+ "Content-Length: 3\r\n"259+ "\r\n"260+ "Hi!";261private int connectionCount = 0;262263HttpServer() throws Exception {264super("HttpServer Thread");265266KeyStore ks = KeyStore.getInstance("JKS");267ks.load(new FileInputStream(keystorefile), passphrase.toCharArray());268KeyManagerFactory factory = KeyManagerFactory.getInstance("SunX509");269factory.init(ks, passphrase.toCharArray());270SSLContext ctx = SSLContext.getInstance("TLS");271ctx.init(factory.getKeyManagers(), null, null);272273sslSocketFactory = ctx.getSocketFactory();274275// Create the server that the test wants to connect to via the proxy276serverSocket = ServerSocketFactory.getDefault().createServerSocket();277serverSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 0));278}279280public int getPort() { return serverSocket.getLocalPort(); }281282@Override283public void close() throws IOException { serverSocket.close(); }284285@Override286public void run() {287try {288while (connectionCount++ < 2) {289Socket socket = serverSocket.accept();290System.out.println("Server: NEW CONNECTION "291+ connectionCount);292293SSLSocket sslSocket = (SSLSocket) sslSocketFactory294.createSocket(socket,null, getPort(), false);295sslSocket.setUseClientMode(false);296sslSocket.startHandshake();297298String firstLine = readHeader(sslSocket.getInputStream());299if (firstLine != null && firstLine.contains("CONNECT")) {300System.out.println("Server: BUG! HTTP CONNECT"301+ " encountered: " + firstLine);302connectInServer.set(true);303}304305// write the success response, the request body is not read.306// close only output and keep input open.307OutputStream out = sslSocket.getOutputStream();308out.write(serverResponse.getBytes("UTF-8"));309socket.shutdownOutput();310}311} catch (IOException e) {312e.printStackTrace();313}314}315}316317/**318* read the header and return only the first line.319*320* @param inputStream the stream to read from321* @return the first line of the stream322* @throws IOException if reading failed323*/324private static String readHeader(InputStream inputStream)325throws IOException {326String line;327String firstLine = null;328while ((line = readLine(inputStream)) != null && line.length() > 0) {329if (firstLine == null) {330firstLine = line;331}332}333334return firstLine;335}336337/**338* read a line from stream.339*340* @param inputStream the stream to read from341* @return the line342* @throws IOException if reading failed343*/344private static String readLine(InputStream inputStream)345throws IOException {346final StringBuilder line = new StringBuilder();347int ch;348while ((ch = inputStream.read()) != -1) {349if (ch == '\r') {350continue;351}352353if (ch == '\n') {354break;355}356357line.append((char) ch);358}359360return line.toString();361}362363private SSLSocketFactory createTestSSLSocketFactory() {364365HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {366@Override367public boolean verify(String hostname, SSLSession sslSession) {368// ignore the cert's CN; it's not important to this test369return true;370}371});372373// Set up the socket factory to use a trust manager that trusts all374// certs, since trust validation isn't important to this test375final TrustManager[] trustAllCertChains = new TrustManager[] {376new X509TrustManager() {377@Override378public X509Certificate[] getAcceptedIssuers() {379return null;380}381382@Override383public void checkClientTrusted(X509Certificate[] certs,384String authType) {385}386387@Override388public void checkServerTrusted(X509Certificate[] certs,389String authType) {390}391}392};393394final SSLContext sc;395try {396sc = SSLContext.getInstance("TLS");397} catch (NoSuchAlgorithmException e) {398throw new RuntimeException(e);399}400401try {402sc.init(null, trustAllCertChains, new java.security.SecureRandom());403} catch (KeyManagementException e) {404throw new RuntimeException(e);405}406407return sc.getSocketFactory();408}409}410411412