1 /*
2  * Copyright (c) 2001, 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  *
26  * This class is used by the regression test ClientHelloRead.java
27  * This class includes a proxy server that processes HTTP CONNECT requests,
28  * and tunnels the data between the client and server once the CONNECT
29  * request is accepted. It is provided to introduce some delay in the network
30  * traffic between the client and server.
31  * This test is to make sure that ClientHello is properly read by the server,
32  * while facing network delays.
33  */
34 
35 import java.io.*;
36 import java.net.*;
37 import javax.net.ssl.*;
38 import javax.net.ServerSocketFactory;
39 import sun.net.www.MessageHeader;
40 
41 public class ProxyTunnelServer extends Thread {
42 
43     private static ServerSocket ss = null;
44 
45     // client requesting for a tunnel
46     private Socket clientSocket = null;
47 
48     /*
49      * Origin server's address and port that the client
50      * wants to establish the tunnel for communication.
51      */
52     private InetAddress serverInetAddr;
53     private int serverPort;
54 
ProxyTunnelServer()55     public ProxyTunnelServer() throws IOException {
56         if (ss == null) {
57           ss = (ServerSocket) ServerSocketFactory.getDefault().
58           createServerSocket(0);
59         }
60     }
61 
run()62     public void run() {
63         try {
64             clientSocket = ss.accept();
65             processRequests();
66         } catch (Exception e) {
67             System.out.println("Proxy Failed: " + e);
68             e.printStackTrace();
69             try {
70                 ss.close();
71             }
72             catch (IOException excep) {
73                 System.out.println("ProxyServer close error: " + excep);
74                 excep.printStackTrace();
75             }
76           }
77     }
78 
79     /*
80      * Processes the CONNECT requests
81      */
processRequests()82     private void processRequests() throws Exception {
83 
84         InputStream in = clientSocket.getInputStream();
85         MessageHeader response = new MessageHeader(in);
86         String statusLine = response.getValue(0);
87 
88         if (statusLine.startsWith("CONNECT")) {
89             // retrieve the host and port info from the response line
90             retrieveConnectInfo(statusLine);
91             respondForConnect();
92             doTunnel();
93             ss.close();
94         } else {
95             System.out.println("proxy server: processes only "
96                                    + "CONNECT method requests, recieved: "
97                                    + statusLine);
98         }
99     }
100 
respondForConnect()101     private void respondForConnect() throws Exception {
102         OutputStream out = clientSocket.getOutputStream();
103         PrintWriter pout = new PrintWriter(out);
104         pout.println("HTTP/1.1 200 OK");
105         pout.println();
106         pout.flush();
107     }
108 
109     /*
110      * note: Tunneling has to be provided in both directions, i.e
111      * from client->server and server->client, even if the application
112      * data may be unidirectional, SSL handshaking data flows in either
113      * direction.
114      */
doTunnel()115     private void doTunnel() throws Exception {
116         Socket serverSocket = new Socket(serverInetAddr, serverPort);
117 
118         // delay the write from client -> server
119         ProxyTunnel clientToServer = new ProxyTunnel(
120                                 clientSocket, serverSocket, true);
121         ProxyTunnel serverToClient = new ProxyTunnel(
122                                 serverSocket, clientSocket, false);
123         clientToServer.start();
124         serverToClient.start();
125 
126         clientToServer.join();
127         serverToClient.join();
128 
129         clientToServer.close();
130         serverToClient.close();
131     }
132 
133     /*
134      * This inner class provides unidirectional data flow through the sockets
135      * by continuously copying bytes from the input socket onto the output
136      * socket, until both sockets are open and EOF has not been received.
137      */
138     class ProxyTunnel extends Thread {
139         Socket sockIn;
140         Socket sockOut;
141         InputStream input;
142         OutputStream output;
143         boolean delayedWrite;
144 
ProxyTunnel(Socket sockIn, Socket sockOut, boolean delayedWrite)145         public ProxyTunnel(Socket sockIn, Socket sockOut, boolean delayedWrite)
146         throws Exception {
147             this.sockIn = sockIn;
148             this.sockOut = sockOut;
149             input = sockIn.getInputStream();
150             output = sockOut.getOutputStream();
151             this.delayedWrite = delayedWrite;
152         }
153 
run()154         public void run() {
155             // the buffer size of < 47 introduces delays in availability
156             // of chunks of client handshake data
157             int BUFFER_SIZE = 40;
158             byte[] buf = new byte[BUFFER_SIZE];
159             int bytesRead = 0;
160             int count = 0;  // keep track of the amount of data transfer
161 
162             try {
163                 while ((bytesRead = input.read(buf)) >= 0) {
164                     if (delayedWrite) {
165                         try {
166                             this.sleep(1);
167                         } catch (InterruptedException excep) {
168                             System.out.println(excep);
169                           }
170                     }
171                     output.write(buf, 0, bytesRead);
172                     output.flush();
173                     count += bytesRead;
174                 }
175             } catch (IOException e) {
176                 /*
177                  * The peer end has closed the connection
178                  * we will close the tunnel
179                  */
180                 close();
181               }
182         }
183 
close()184         public void close() {
185             try {
186                 if (!sockIn.isClosed())
187                     sockIn.close();
188                 if (!sockOut.isClosed())
189                     sockOut.close();
190             } catch (IOException ignored) { }
191         }
192     }
193 
194     /*
195      ***************************************************************
196      *                  helper methods follow
197      ***************************************************************
198      */
199 
200     /*
201      * This method retrieves the hostname and port of the destination
202      * that the connect request wants to establish a tunnel for
203      * communication.
204      * The input, connectStr is of the form:
205      *                          CONNECT server-name:server-port HTTP/1.x
206      */
retrieveConnectInfo(String connectStr)207     void retrieveConnectInfo(String connectStr) throws Exception {
208         int starti;
209         int endi;
210         String connectInfo;
211         String serverName = null;
212         try {
213             starti = connectStr.indexOf(' ');
214             endi = connectStr.lastIndexOf(' ');
215             connectInfo = connectStr.substring(starti+1, endi).trim();
216             // retrieve server name and port
217             endi = connectInfo.indexOf(':');
218             serverName = connectInfo.substring(0, endi);
219             serverPort = Integer.parseInt(connectInfo.substring(endi+1));
220         } catch (Exception e) {
221             throw new IOException("Proxy recieved a request: "
222                                         + connectStr);
223           }
224         serverInetAddr = InetAddress.getByName(serverName);
225     }
226 
getPort()227     public int getPort() {
228         return ss.getLocalPort();
229     }
230 }
231