Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/test/sun/net/www/protocol/http/ProxyTunnelServer.java
38867 views
/*1* Copyright (c) 2002, 2013, 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*/2223/**24*25* This class includes a proxy server that processes HTTP CONNECT requests,26* and tunnels the data from the client to the server, once the CONNECT27* request is accepted.28* It is used by the TunnelThroughProxy test.29*/3031import java.io.*;32import java.net.*;33import java.util.Base64;34import javax.net.ssl.*;35import javax.net.ServerSocketFactory;36import sun.net.www.*;3738public class ProxyTunnelServer extends Thread {3940private static ServerSocket ss = null;41/*42* holds the registered user's username and password43* only one such entry is maintained44*/45private String userPlusPass;4647// client requesting for a tunnel48private Socket clientSocket = null;4950/*51* Origin server's address and port that the client52* wants to establish the tunnel for communication.53*/54private InetAddress serverInetAddr;55private int serverPort;5657/*58* denote whether the proxy needs to authorize59* CONNECT requests.60*/61static boolean needAuth = false;6263public ProxyTunnelServer() throws IOException {64if (ss == null) {65ss = (ServerSocket) ServerSocketFactory.getDefault().66createServerSocket(0);67}68}6970public void needUserAuth(boolean auth) {71needAuth = auth;72}7374/*75* register users with the proxy, by providing username and76* password. The username and password are used for authorizing the77* user when a CONNECT request is made and needAuth is set to true.78*/79public void setUserAuth(String uname, String passwd) {80userPlusPass = uname + ":" + passwd;81}8283public void run() {84try {85clientSocket = ss.accept();86processRequests();87} catch (Exception e) {88System.out.println("Proxy Failed: " + e);89e.printStackTrace();90try {91ss.close();92}93catch (IOException excep) {94System.out.println("ProxyServer close error: " + excep);95excep.printStackTrace();96}97}98}99100/*101* Processes the CONNECT requests, if needAuth is set to true, then102* the name and password are extracted from the Proxy-Authorization header103* of the request. They are checked against the one that is registered,104* if there is a match, connection is set in tunneling mode. If105* needAuth is set to false, Proxy-Authorization checks are not made106*/107private void processRequests() throws Exception {108109InputStream in = clientSocket.getInputStream();110MessageHeader mheader = new MessageHeader(in);111String statusLine = mheader.getValue(0);112113if (statusLine.startsWith("CONNECT")) {114// retrieve the host and port info from the status-line115// retrieveConnectInfo(statusLine);116if (needAuth) {117String authInfo;118if ((authInfo = mheader.findValue("Proxy-Authorization"))119!= null) {120if (authenticate(authInfo)) {121needAuth = false;122System.out.println(123"Proxy: client authentication successful");124}125}126}127respondForConnect(needAuth);128129// connection set to the tunneling mode130if (!needAuth) {131// doTunnel();132/*133* done with tunneling, we process only one successful134* tunneling request135*/136ss.close();137} else {138// we may get another request with Proxy-Authorization set139in.close();140clientSocket.close();141restart();142}143} else {144System.out.println("proxy server: processes only "145+ "CONNECT method requests, recieved: "146+ statusLine);147}148}149150private void respondForConnect(boolean needAuth) throws Exception {151152OutputStream out = clientSocket.getOutputStream();153PrintWriter pout = new PrintWriter(out);154155if (needAuth) {156pout.println("HTTP/1.1 407 Proxy Auth Required");157pout.println("Proxy-Authenticate: Basic realm=\"WallyWorld\"");158pout.println();159pout.flush();160out.close();161} else {162pout.println("HTTP/1.1 500 Server Error");163pout.println();164pout.flush();165out.close();166}167}168169private void restart() throws IOException {170(new Thread(this)).start();171}172173/*sc174* note: Tunneling has to be provided in both directions, i.e175* from client->server and server->client, even if the application176* data may be unidirectional, SSL handshaking data flows in either177* direction.178*/179private void doTunnel() throws Exception {180181Socket serverSocket = new Socket(serverInetAddr, serverPort);182ProxyTunnel clientToServer = new ProxyTunnel(183clientSocket, serverSocket);184ProxyTunnel serverToClient = new ProxyTunnel(185serverSocket, clientSocket);186clientToServer.start();187serverToClient.start();188System.out.println("Proxy: Started tunneling.......");189190clientToServer.join();191serverToClient.join();192System.out.println("Proxy: Finished tunneling........");193194clientToServer.close();195serverToClient.close();196}197198/*199* This inner class provides unidirectional data flow through the sockets200* by continuously copying bytes from the input socket onto the output201* socket, until both sockets are open and EOF has not been received.202*/203class ProxyTunnel extends Thread {204Socket sockIn;205Socket sockOut;206InputStream input;207OutputStream output;208209public ProxyTunnel(Socket sockIn, Socket sockOut)210throws Exception {211this.sockIn = sockIn;212this.sockOut = sockOut;213input = sockIn.getInputStream();214output = sockOut.getOutputStream();215}216217public void run() {218int BUFFER_SIZE = 400;219byte[] buf = new byte[BUFFER_SIZE];220int bytesRead = 0;221int count = 0; // keep track of the amount of data transfer222223try {224while ((bytesRead = input.read(buf)) >= 0) {225output.write(buf, 0, bytesRead);226output.flush();227count += bytesRead;228}229} catch (IOException e) {230/*231* The peer end has closed the connection232* we will close the tunnel233*/234close();235}236}237238public void close() {239try {240if (!sockIn.isClosed())241sockIn.close();242if (!sockOut.isClosed())243sockOut.close();244} catch (IOException ignored) { }245}246}247248/*249***************************************************************250* helper methods follow251***************************************************************252*/253254/*255* This method retrieves the hostname and port of the destination256* that the connect request wants to establish a tunnel for257* communication.258* The input, connectStr is of the form:259* CONNECT server-name:server-port HTTP/1.x260*/261private void retrieveConnectInfo(String connectStr) throws Exception {262int starti;263int endi;264String connectInfo;265String serverName = null;266try {267starti = connectStr.indexOf(' ');268endi = connectStr.lastIndexOf(' ');269connectInfo = connectStr.substring(starti+1, endi).trim();270// retrieve server name and port271endi = connectInfo.indexOf(':');272serverName = connectInfo.substring(0, endi);273serverPort = Integer.parseInt(connectInfo.substring(endi+1));274} catch (Exception e) {275throw new IOException("Proxy recieved a request: "276+ connectStr);277}278serverInetAddr = InetAddress.getByName(serverName);279}280281public int getPort() {282return ss.getLocalPort();283}284285/*286* do "basic" authentication, authInfo is of the form:287* Basic <encoded username":"password>288* reference RFC 2617289*/290private boolean authenticate(String authInfo) throws IOException {291boolean matched = false;292try {293authInfo.trim();294int ind = authInfo.indexOf(' ');295String recvdUserPlusPass = authInfo.substring(ind + 1).trim();296// extract encoded (username:passwd297if (userPlusPass.equals(298new String(Base64.getDecoder().decode(recvdUserPlusPass))299)) {300matched = true;301}302} catch (Exception e) {303throw new IOException(304"Proxy received invalid Proxy-Authorization value: "305+ authInfo);306}307return matched;308}309}310311312