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
35import java.io.*;
36import java.net.*;
37import javax.net.ssl.*;
38import javax.net.ServerSocketFactory;
39import sun.net.www.MessageHeader;
40
41public 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
55    public ProxyTunnelServer() throws IOException {
56        if (ss == null) {
57          ss = (ServerSocket) ServerSocketFactory.getDefault().
58          createServerSocket(0);
59        }
60    }
61
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     */
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
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     */
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
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
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
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     */
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
227    public int getPort() {
228        return ss.getLocalPort();
229    }
230}
231