Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/test/sun/net/www/protocol/https/HttpsClient/ProxyTunnelServer.java
38889 views
/*1* Copyright (c) 2001, 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* is accepted.28* It is used by the regression test for the bug fixes: 4323990, 441306929*/30import java.io.*;31import java.net.*;32import javax.net.ssl.*;33import javax.net.ServerSocketFactory;34import sun.net.www.*;35import java.util.Base64;3637public class ProxyTunnelServer extends Thread {3839private static ServerSocket ss = null;40/*41* holds the registered user's username and password42* only one such entry is maintained43*/44private String userPlusPass;4546// client requesting for a tunnel47private Socket clientSocket = null;4849/*50* Origin server's address and port that the client51* wants to establish the tunnel for communication.52*/53private InetAddress serverInetAddr;54private int serverPort;5556/*57* denote whether the proxy needs to authorize58* CONNECT requests.59*/60static boolean needAuth = false;6162public ProxyTunnelServer() throws IOException {63if (ss == null) {64ss = (ServerSocket) ServerSocketFactory.getDefault().65createServerSocket(0);66}67setDaemon(true);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-line115retrieveConnectInfo(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) {131doTunnel();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 200 OK");163pout.println();164pout.flush();165}166}167168private void restart() throws IOException {169(new Thread(this)).start();170}171172/*173* note: Tunneling has to be provided in both directions, i.e174* from client->server and server->client, even if the application175* data may be unidirectional, SSL handshaking data flows in either176* direction.177*/178private void doTunnel() throws Exception {179180Socket serverSocket = new Socket(serverInetAddr, serverPort);181ProxyTunnel clientToServer = new ProxyTunnel(182clientSocket, serverSocket);183ProxyTunnel serverToClient = new ProxyTunnel(184serverSocket, clientSocket);185clientToServer.start();186serverToClient.start();187System.out.println("Proxy: Started tunneling.......");188189clientToServer.join();190serverToClient.join();191System.out.println("Proxy: Finished tunneling........");192193clientToServer.close();194serverToClient.close();195}196197/*198* This inner class provides unidirectional data flow through the sockets199* by continuously copying bytes from the input socket onto the output200* socket, until both sockets are open and EOF has not been received.201*/202class ProxyTunnel extends Thread {203Socket sockIn;204Socket sockOut;205InputStream input;206OutputStream output;207208public ProxyTunnel(Socket sockIn, Socket sockOut)209throws Exception {210this.sockIn = sockIn;211this.sockOut = sockOut;212input = sockIn.getInputStream();213output = sockOut.getOutputStream();214setDaemon(true);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 {262263int starti;264int endi;265String connectInfo;266String serverName = null;267try {268starti = connectStr.indexOf(' ');269endi = connectStr.lastIndexOf(' ');270connectInfo = connectStr.substring(starti+1, endi).trim();271// retrieve server name and port272endi = connectInfo.indexOf(':');273serverName = connectInfo.substring(0, endi);274serverPort = Integer.parseInt(connectInfo.substring(endi+1));275} catch (Exception e) {276throw new IOException("Proxy recieved a request: "277+ connectStr);278}279serverInetAddr = InetAddress.getByName(serverName);280}281282public int getPort() {283return ss.getLocalPort();284}285286/*287* do "basic" authentication, authInfo is of the form:288* Basic <encoded username":"password>289* reference RFC 2617290*/291private boolean authenticate(String authInfo) throws IOException {292boolean matched = false;293try {294authInfo.trim();295int ind = authInfo.indexOf(' ');296String recvdUserPlusPass = authInfo.substring(ind + 1).trim();297298// extract encoded (username:passwd299if (userPlusPass.equals(300new String( Base64.getMimeDecoder()301.decode(recvdUserPlusPass))))302{303matched = true;304}305} catch (Exception e) {306throw new IOException(307"Proxy received invalid Proxy-Authorization value: "308+ authInfo);309}310return matched;311}312}313314315