Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/test/sun/net/www/http/HttpClient/B8025710.java
38868 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 {50// test uses legacy MD5 based cert51Security.setProperty("jdk.certpath.disabledAlgorithms", "");52Security.setProperty("jdk.tls.disabledAlgorithms", "");53new B8025710().runTest();5455if (connectInServer.get())56throw new RuntimeException("TEST FAILED: server got proxy header");57else58System.out.println("TEST PASSED");59}6061private void runTest() throws Exception {62ProxyServer proxyServer = new ProxyServer();63HttpServer httpServer = new HttpServer();64httpServer.start();65proxyServer.start();6667URL url = new URL("https", InetAddress.getLocalHost().getHostName(),68httpServer.getPort(), "/");6970Proxy proxy = new Proxy(Proxy.Type.HTTP, proxyServer.getAddress());7172HttpsURLConnection.setDefaultSSLSocketFactory(createTestSSLSocketFactory());7374// Make two connections. The bug occurs when the second request is made75for (int i = 0; i < 2; i++) {76System.out.println("Client: Requesting " + url.toExternalForm()77+ " via " + proxy.toString()78+ " (attempt " + (i + 1) + " of 2)");7980HttpsURLConnection connection =81(HttpsURLConnection) url.openConnection(proxy);8283connection.setRequestMethod("POST");84connection.setDoInput(true);85connection.setDoOutput(true);86connection.setRequestProperty("User-Agent", "Test/1.0");87connection.getOutputStream().write("Hello, world!".getBytes("UTF-8"));8889if (connection.getResponseCode() != 200) {90System.err.println("Client: Unexpected response code "91+ connection.getResponseCode());92break;93}9495String response = readLine(connection.getInputStream());96if (!"Hi!".equals(response)) {97System.err.println("Client: Unexpected response body: "98+ response);99}100}101httpServer.close();102proxyServer.close();103httpServer.join();104proxyServer.join();105}106107class ProxyServer extends Thread implements Closeable {108109private final ServerSocket proxySocket;110private final Pattern connectLinePattern =111Pattern.compile("^CONNECT ([^: ]+):([0-9]+) HTTP/[0-9.]+$");112private final String PROXY_RESPONSE =113"HTTP/1.0 200 Connection Established\r\n"114+ "Proxy-Agent: TestProxy/1.0\r\n"115+ "\r\n";116117ProxyServer() throws Exception {118super("ProxyServer Thread");119120// Create the http proxy server socket121proxySocket = ServerSocketFactory.getDefault().createServerSocket();122proxySocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 0));123}124125public SocketAddress getAddress() { return proxySocket.getLocalSocketAddress(); }126127@Override128public void close() throws IOException {129proxySocket.close();130}131132@Override133public void run() {134ArrayList<Thread> threads = new ArrayList<>();135int connectionCount = 0;136try {137while (connectionCount++ < 2) {138final Socket clientSocket = proxySocket.accept();139final int proxyConnectionCount = connectionCount;140System.out.println("Proxy: NEW CONNECTION "141+ proxyConnectionCount);142143Thread t = new Thread("ProxySocket" + proxyConnectionCount) {144@Override145public void run() {146try {147String firstLine =148readHeader(clientSocket.getInputStream());149150Matcher connectLineMatcher =151connectLinePattern.matcher(firstLine);152if (!connectLineMatcher.matches()) {153System.out.println("Proxy: Unexpected"154+ " request to the proxy: "155+ firstLine);156return;157}158159String host = connectLineMatcher.group(1);160String portStr = connectLineMatcher.group(2);161int port = Integer.parseInt(portStr);162163Socket serverSocket = SocketFactory.getDefault()164.createSocket(host, port);165166clientSocket.getOutputStream()167.write(PROXY_RESPONSE.getBytes("UTF-8"));168169ProxyTunnel copyToClient =170new ProxyTunnel(serverSocket, clientSocket);171ProxyTunnel copyToServer =172new ProxyTunnel(clientSocket, serverSocket);173174copyToClient.start();175copyToServer.start();176177copyToClient.join();178// here copyToClient.close() would not provoke the179// bug ( since it would trigger the retry logic in180// HttpURLConnction.writeRequests ), so close only181// the output to get the connection in this state.182clientSocket.shutdownOutput();183184try {185Thread.sleep(3000);186} catch (InterruptedException ignored) { }187188// now close all connections to finish the test189copyToServer.close();190copyToClient.close();191} catch (IOException | NumberFormatException192| InterruptedException e) {193e.printStackTrace();194}195}196};197threads.add(t);198t.start();199}200for (Thread t: threads)201t.join();202} catch (IOException | InterruptedException e) {203e.printStackTrace();204}205}206}207208/**209* This inner class provides unidirectional data flow through the sockets210* by continuously copying bytes from the input socket onto the output211* socket, until both sockets are open and EOF has not been received.212*/213class ProxyTunnel extends Thread {214private final Socket sockIn;215private final Socket sockOut;216private final InputStream input;217private final OutputStream output;218219public ProxyTunnel(Socket sockIn, Socket sockOut) throws IOException {220super("ProxyTunnel");221this.sockIn = sockIn;222this.sockOut = sockOut;223input = sockIn.getInputStream();224output = sockOut.getOutputStream();225}226227public void run() {228byte[] buf = new byte[8192];229int bytesRead;230231try {232while ((bytesRead = input.read(buf)) >= 0) {233output.write(buf, 0, bytesRead);234output.flush();235}236} catch (IOException ignored) {237close();238}239}240241public void close() {242try {243if (!sockIn.isClosed())244sockIn.close();245if (!sockOut.isClosed())246sockOut.close();247} catch (IOException ignored) { }248}249}250251/**252* the server thread253*/254class HttpServer extends Thread implements Closeable {255256private final ServerSocket serverSocket;257private final SSLSocketFactory sslSocketFactory;258private final String serverResponse =259"HTTP/1.1 200 OK\r\n"260+ "Content-Type: text/plain\r\n"261+ "Content-Length: 3\r\n"262+ "\r\n"263+ "Hi!";264private int connectionCount = 0;265266HttpServer() throws Exception {267super("HttpServer Thread");268269KeyStore ks = KeyStore.getInstance("JKS");270ks.load(new FileInputStream(keystorefile), passphrase.toCharArray());271KeyManagerFactory factory = KeyManagerFactory.getInstance("SunX509");272factory.init(ks, passphrase.toCharArray());273SSLContext ctx = SSLContext.getInstance("TLS");274ctx.init(factory.getKeyManagers(), null, null);275276sslSocketFactory = ctx.getSocketFactory();277278// Create the server that the test wants to connect to via the proxy279serverSocket = ServerSocketFactory.getDefault().createServerSocket();280serverSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 0));281}282283public int getPort() { return serverSocket.getLocalPort(); }284285@Override286public void close() throws IOException { serverSocket.close(); }287288@Override289public void run() {290try {291while (connectionCount++ < 2) {292Socket socket = serverSocket.accept();293System.out.println("Server: NEW CONNECTION "294+ connectionCount);295296SSLSocket sslSocket = (SSLSocket) sslSocketFactory297.createSocket(socket,null, getPort(), false);298sslSocket.setUseClientMode(false);299sslSocket.startHandshake();300301String firstLine = readHeader(sslSocket.getInputStream());302if (firstLine != null && firstLine.contains("CONNECT")) {303System.out.println("Server: BUG! HTTP CONNECT"304+ " encountered: " + firstLine);305connectInServer.set(true);306}307308// write the success response, the request body is not read.309// close only output and keep input open.310OutputStream out = sslSocket.getOutputStream();311out.write(serverResponse.getBytes("UTF-8"));312socket.shutdownOutput();313}314} catch (IOException e) {315e.printStackTrace();316}317}318}319320/**321* read the header and return only the first line.322*323* @param inputStream the stream to read from324* @return the first line of the stream325* @throws IOException if reading failed326*/327private static String readHeader(InputStream inputStream)328throws IOException {329String line;330String firstLine = null;331while ((line = readLine(inputStream)) != null && line.length() > 0) {332if (firstLine == null) {333firstLine = line;334}335}336337return firstLine;338}339340/**341* read a line from stream.342*343* @param inputStream the stream to read from344* @return the line345* @throws IOException if reading failed346*/347private static String readLine(InputStream inputStream)348throws IOException {349final StringBuilder line = new StringBuilder();350int ch;351while ((ch = inputStream.read()) != -1) {352if (ch == '\r') {353continue;354}355356if (ch == '\n') {357break;358}359360line.append((char) ch);361}362363return line.toString();364}365366private SSLSocketFactory createTestSSLSocketFactory() {367368HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {369@Override370public boolean verify(String hostname, SSLSession sslSession) {371// ignore the cert's CN; it's not important to this test372return true;373}374});375376// Set up the socket factory to use a trust manager that trusts all377// certs, since trust validation isn't important to this test378final TrustManager[] trustAllCertChains = new TrustManager[] {379new X509TrustManager() {380@Override381public X509Certificate[] getAcceptedIssuers() {382return null;383}384385@Override386public void checkClientTrusted(X509Certificate[] certs,387String authType) {388}389390@Override391public void checkServerTrusted(X509Certificate[] certs,392String authType) {393}394}395};396397final SSLContext sc;398try {399sc = SSLContext.getInstance("TLS");400} catch (NoSuchAlgorithmException e) {401throw new RuntimeException(e);402}403404try {405sc.init(null, trustAllCertChains, new java.security.SecureRandom());406} catch (KeyManagementException e) {407throw new RuntimeException(e);408}409410return sc.getSocketFactory();411}412}413414415