1 /*
   2  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @bug 6226610 6973030
  27  * @summary HTTP tunnel connections send user headers to proxy
  28  * @modules java.base/sun.net.www
  29  * @run main/othervm B6226610
  30  */
  31 
  32 /* This class includes a proxy server that processes the HTTP CONNECT request,
  33  * and validates that the request does not have the user defined header in it.
  34  * The proxy server always returns 400 Bad Request so that the Http client
  35  * will not try to proceed with the connection as there is no back end http server.
  36  */
  37 
  38 import java.io.*;
  39 import java.net.*;
  40 import sun.net.www.MessageHeader;
  41 
  42 public class B6226610 {
  43     static HeaderCheckerProxyTunnelServer proxy;
  44 
  45     public static void main(String[] args) throws Exception
  46     {
  47         proxy = new HeaderCheckerProxyTunnelServer();
  48         proxy.start();
  49 
  50         String hostname = InetAddress.getLocalHost().getHostName();
  51 
  52         try {
  53            URL u = new URL("https://" + hostname + "/");
  54            System.out.println("Connecting to " + u);
  55            InetSocketAddress proxyAddr = new InetSocketAddress(hostname, proxy.getLocalPort());
  56            java.net.URLConnection c = u.openConnection(new Proxy(Proxy.Type.HTTP, proxyAddr));
  57 
  58            /* I want this header to go to the destination server only, protected
  59             * by SSL
  60             */
  61            c.setRequestProperty("X-TestHeader", "value");
  62            c.connect();
  63 
  64          } catch (IOException e) {
  65             if ( e.getMessage().equals("Unable to tunnel through proxy. Proxy returns \"HTTP/1.1 400 Bad Request\"") )
  66             {
  67                // OK. Proxy will always return 400 so that the main thread can terminate correctly.
  68             }
  69             else
  70                System.out.println(e);
  71          } finally {
  72              if (proxy != null) proxy.shutdown();
  73          }
  74 
  75          if (HeaderCheckerProxyTunnelServer.failed)
  76             throw new RuntimeException("Test failed; see output");
  77     }
  78 }
  79 
  80 class HeaderCheckerProxyTunnelServer extends Thread
  81 {
  82     public static boolean failed = false;
  83 
  84     private static ServerSocket ss = null;
  85 
  86     // client requesting for a tunnel
  87     private Socket clientSocket = null;
  88 
  89     /*
  90      * Origin server's address and port that the client
  91      * wants to establish the tunnel for communication.
  92      */
  93     private InetAddress serverInetAddr;
  94     private int serverPort;
  95 
  96     public HeaderCheckerProxyTunnelServer() throws IOException
  97     {
  98        if (ss == null) {
  99           ss = new ServerSocket(0);
 100        }
 101     }
 102 
 103     void shutdown() {
 104         try { ss.close(); } catch (IOException e) {}
 105     }
 106 
 107     public void run()
 108     {
 109         try {
 110             clientSocket = ss.accept();
 111             processRequests();
 112         } catch (IOException e) {
 113             System.out.println("Proxy Failed: " + e);
 114             e.printStackTrace();
 115             try {
 116                    ss.close();
 117             }
 118             catch (IOException excep) {
 119                System.out.println("ProxyServer close error: " + excep);
 120                excep.printStackTrace();
 121             }
 122         }
 123     }
 124 
 125     /**
 126      * Returns the port on which the proxy is accepting connections.
 127      */
 128     public int getLocalPort() {
 129         return ss.getLocalPort();
 130     }
 131 
 132     /*
 133      * Processes the CONNECT request
 134      */
 135     private void processRequests() throws IOException
 136     {
 137         InputStream in = clientSocket.getInputStream();
 138         MessageHeader mheader = new MessageHeader(in);
 139         String statusLine = mheader.getValue(0);
 140 
 141         if (statusLine.startsWith("CONNECT")) {
 142            // retrieve the host and port info from the status-line
 143            retrieveConnectInfo(statusLine);
 144 
 145            if (mheader.findValue("X-TestHeader") != null) {
 146              System.out.println("Proxy should not receive user defined headers for tunneled requests");
 147              failed = true;
 148            }
 149 
 150            // 6973030
 151            String value;
 152            if ((value = mheader.findValue("Proxy-Connection")) == null ||
 153                 !value.equals("keep-alive")) {
 154              System.out.println("Proxy-Connection:keep-alive not being sent");
 155              failed = true;
 156            }
 157 
 158            //This will allow the main thread to terminate without trying to perform the SSL handshake.
 159            send400();
 160 
 161            in.close();
 162            clientSocket.close();
 163            ss.close();
 164         }
 165         else {
 166             System.out.println("proxy server: processes only "
 167                                    + "CONNECT method requests, recieved: "
 168                                    + statusLine);
 169         }
 170     }
 171 
 172     private void send400() throws IOException
 173     {
 174         OutputStream out = clientSocket.getOutputStream();
 175         PrintWriter pout = new PrintWriter(out);
 176 
 177         pout.println("HTTP/1.1 400 Bad Request");
 178         pout.println();
 179         pout.flush();
 180     }
 181 
 182     private void restart() throws IOException {
 183          (new Thread(this)).start();
 184     }
 185 
 186     /*
 187      * This method retrieves the hostname and port of the destination
 188      * that the connect request wants to establish a tunnel for
 189      * communication.
 190      * The input, connectStr is of the form:
 191      *                          CONNECT server-name:server-port HTTP/1.x
 192      */
 193     private void retrieveConnectInfo(String connectStr) throws IOException {
 194 
 195         int starti;
 196         int endi;
 197         String connectInfo;
 198         String serverName = null;
 199         try {
 200             starti = connectStr.indexOf(' ');
 201             endi = connectStr.lastIndexOf(' ');
 202             connectInfo = connectStr.substring(starti+1, endi).trim();
 203             // retrieve server name and port
 204             endi = connectInfo.indexOf(':');
 205             serverName = connectInfo.substring(0, endi);
 206             serverPort = Integer.parseInt(connectInfo.substring(endi+1));
 207         } catch (Exception e) {
 208             throw new IOException("Proxy recieved a request: "
 209                                         + connectStr);
 210           }
 211         serverInetAddr = InetAddress.getByName(serverName);
 212     }
 213 }