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  * @run main/othervm B6226610
  28  * @summary HTTP tunnel connections send user headers to proxy
  29  */
  30 
  31 /* This class includes a proxy server that processes the HTTP CONNECT request,
  32  * and validates that the request does not have the user defined header in it.
  33  * The proxy server always returns 400 Bad Request so that the Http client
  34  * will not try to proceed with the connection as there is no back end http server.
  35  */
  36 
  37 import java.io.*;
  38 import java.net.*;
  39 import sun.net.www.MessageHeader;
  40 
  41 public class B6226610 {
  42     static HeaderCheckerProxyTunnelServer proxy;
  43 
  44     public static void main(String[] args) throws Exception
  45     {
  46         proxy = new HeaderCheckerProxyTunnelServer();
  47         proxy.start();
  48 
  49         String hostname = InetAddress.getLocalHost().getHostName();
  50 
  51         try {
  52            URL u = new URL("https://" + hostname + "/");
  53            System.out.println("Connecting to " + u);
  54            InetSocketAddress proxyAddr = new InetSocketAddress(hostname, proxy.getLocalPort());
  55            java.net.URLConnection c = u.openConnection(new Proxy(Proxy.Type.HTTP, proxyAddr));
  56 
  57            /* I want this header to go to the destination server only, protected
  58             * by SSL
  59             */
  60            c.setRequestProperty("X-TestHeader", "value");
  61            c.connect();
  62 
  63          } catch (IOException e) {
  64             if ( e.getMessage().equals("Unable to tunnel through proxy. Proxy returns \"HTTP/1.1 400 Bad Request\"") )
  65             {
  66                // OK. Proxy will always return 400 so that the main thread can terminate correctly.
  67             }
  68             else
  69                System.out.println(e);
  70          } finally {
  71              if (proxy != null) proxy.shutdown();
  72          }
  73 
  74          if (HeaderCheckerProxyTunnelServer.failed)
  75             throw new RuntimeException("Test failed; see output");
  76     }
  77 }
  78 
  79 class HeaderCheckerProxyTunnelServer extends Thread
  80 {
  81     public static boolean failed = false;
  82 
  83     private static ServerSocket ss = null;
  84 
  85     // client requesting for a tunnel
  86     private Socket clientSocket = null;
  87 
  88     /*
  89      * Origin server's address and port that the client
  90      * wants to establish the tunnel for communication.
  91      */
  92     private InetAddress serverInetAddr;
  93     private int serverPort;
  94 
  95     public HeaderCheckerProxyTunnelServer() throws IOException
  96     {
  97        if (ss == null) {
  98           ss = new ServerSocket(0);
  99        }
 100     }
 101 
 102     void shutdown() {
 103         try { ss.close(); } catch (IOException e) {}
 104     }
 105 
 106     public void run()
 107     {
 108         try {
 109             clientSocket = ss.accept();
 110             processRequests();
 111         } catch (IOException e) {
 112             System.out.println("Proxy Failed: " + e);
 113             e.printStackTrace();
 114             try {
 115                    ss.close();
 116             }
 117             catch (IOException excep) {
 118                System.out.println("ProxyServer close error: " + excep);
 119                excep.printStackTrace();
 120             }
 121         }
 122     }
 123 
 124     /**
 125      * Returns the port on which the proxy is accepting connections.
 126      */
 127     public int getLocalPort() {
 128         return ss.getLocalPort();
 129     }
 130 
 131     /*
 132      * Processes the CONNECT request
 133      */
 134     private void processRequests() throws IOException
 135     {
 136         InputStream in = clientSocket.getInputStream();
 137         MessageHeader mheader = new MessageHeader(in);
 138         String statusLine = mheader.getValue(0);
 139 
 140         if (statusLine.startsWith("CONNECT")) {
 141            // retrieve the host and port info from the status-line
 142            retrieveConnectInfo(statusLine);
 143 
 144            if (mheader.findValue("X-TestHeader") != null) {
 145              System.out.println("Proxy should not receive user defined headers for tunneled requests");
 146              failed = true;
 147            }
 148 
 149            // 6973030
 150            String value;
 151            if ((value = mheader.findValue("Proxy-Connection")) == null ||
 152                 !value.equals("keep-alive")) {
 153              System.out.println("Proxy-Connection:keep-alive not being sent");
 154              failed = true;
 155            }
 156 
 157            //This will allow the main thread to terminate without trying to perform the SSL handshake.
 158            send400();
 159 
 160            in.close();
 161            clientSocket.close();
 162            ss.close();
 163         }
 164         else {
 165             System.out.println("proxy server: processes only "
 166                                    + "CONNECT method requests, recieved: "
 167                                    + statusLine);
 168         }
 169     }
 170 
 171     private void send400() throws IOException
 172     {
 173         OutputStream out = clientSocket.getOutputStream();
 174         PrintWriter pout = new PrintWriter(out);
 175 
 176         pout.println("HTTP/1.1 400 Bad Request");
 177         pout.println();
 178         pout.flush();
 179     }
 180 
 181     private void restart() throws IOException {
 182          (new Thread(this)).start();
 183     }
 184 
 185     /*
 186      * This method retrieves the hostname and port of the destination
 187      * that the connect request wants to establish a tunnel for
 188      * communication.
 189      * The input, connectStr is of the form:
 190      *                          CONNECT server-name:server-port HTTP/1.x
 191      */
 192     private void retrieveConnectInfo(String connectStr) throws IOException {
 193 
 194         int starti;
 195         int endi;
 196         String connectInfo;
 197         String serverName = null;
 198         try {
 199             starti = connectStr.indexOf(' ');
 200             endi = connectStr.lastIndexOf(' ');
 201             connectInfo = connectStr.substring(starti+1, endi).trim();
 202             // retrieve server name and port
 203             endi = connectInfo.indexOf(':');
 204             serverName = connectInfo.substring(0, endi);
 205             serverPort = Integer.parseInt(connectInfo.substring(endi+1));
 206         } catch (Exception e) {
 207             throw new IOException("Proxy recieved a request: "
 208                                         + connectStr);
 209           }
 210         serverInetAddr = InetAddress.getByName(serverName);
 211     }
 212 }