Path: blob/master/test/jdk/sun/net/www/protocol/https/HttpsClient/ProxyTunnelServer.java
41161 views
/*1* Copyright (c) 2001, 2019, 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 {63this(null); // use wildcard64}6566public ProxyTunnelServer(InetAddress proxyAddress) throws IOException {67if (ss == null) {68ss = (ServerSocket) ServerSocketFactory.getDefault().69createServerSocket();70ss.bind(new InetSocketAddress(proxyAddress, 0));71}72setDaemon(true);73}7475public void needUserAuth(boolean auth) {76needAuth = auth;77}7879/*80* register users with the proxy, by providing username and81* password. The username and password are used for authorizing the82* user when a CONNECT request is made and needAuth is set to true.83*/84public void setUserAuth(String uname, String passwd) {85userPlusPass = uname + ":" + passwd;86}8788public void run() {89try {90clientSocket = ss.accept();91processRequests();92} catch (Exception e) {93System.out.println("Proxy Failed: " + e);94e.printStackTrace();95try {96ss.close();97}98catch (IOException excep) {99System.out.println("ProxyServer close error: " + excep);100excep.printStackTrace();101}102}103}104105/*106* Processes the CONNECT requests, if needAuth is set to true, then107* the name and password are extracted from the Proxy-Authorization header108* of the request. They are checked against the one that is registered,109* if there is a match, connection is set in tunneling mode. If110* needAuth is set to false, Proxy-Authorization checks are not made111*/112private void processRequests() throws Exception {113114InputStream in = clientSocket.getInputStream();115MessageHeader mheader = new MessageHeader(in);116String statusLine = mheader.getValue(0);117118if (statusLine.startsWith("CONNECT")) {119// retrieve the host and port info from the status-line120retrieveConnectInfo(statusLine);121if (needAuth) {122String authInfo;123if ((authInfo = mheader.findValue("Proxy-Authorization"))124!= null) {125if (authenticate(authInfo)) {126needAuth = false;127System.out.println(128"Proxy: client authentication successful");129}130}131}132respondForConnect(needAuth);133134// connection set to the tunneling mode135if (!needAuth) {136doTunnel();137/*138* done with tunneling, we process only one successful139* tunneling request140*/141ss.close();142} else {143// we may get another request with Proxy-Authorization set144in.close();145clientSocket.close();146restart();147}148} else {149System.out.println("proxy server: processes only "150+ "CONNECT method requests, recieved: "151+ statusLine);152}153}154155private void respondForConnect(boolean needAuth) throws Exception {156157OutputStream out = clientSocket.getOutputStream();158PrintWriter pout = new PrintWriter(out);159160if (needAuth) {161pout.println("HTTP/1.1 407 Proxy Auth Required");162pout.println("Proxy-Authenticate: Basic realm=\"WallyWorld\"");163pout.println();164pout.flush();165out.close();166} else {167pout.println("HTTP/1.1 200 OK");168pout.println();169pout.flush();170}171}172173private void restart() throws IOException {174(new Thread(this)).start();175}176177/*178* note: Tunneling has to be provided in both directions, i.e179* from client->server and server->client, even if the application180* data may be unidirectional, SSL handshaking data flows in either181* direction.182*/183private void doTunnel() throws Exception {184185Socket serverSocket = new Socket(serverInetAddr, serverPort);186ProxyTunnel clientToServer = new ProxyTunnel(187clientSocket, serverSocket);188ProxyTunnel serverToClient = new ProxyTunnel(189serverSocket, clientSocket);190clientToServer.start();191serverToClient.start();192System.out.println("Proxy: Started tunneling.......");193194clientToServer.join();195serverToClient.join();196System.out.println("Proxy: Finished tunneling........");197198clientToServer.close();199serverToClient.close();200}201202/*203* This inner class provides unidirectional data flow through the sockets204* by continuously copying bytes from the input socket onto the output205* socket, until both sockets are open and EOF has not been received.206*/207class ProxyTunnel extends Thread {208Socket sockIn;209Socket sockOut;210InputStream input;211OutputStream output;212213public ProxyTunnel(Socket sockIn, Socket sockOut)214throws Exception {215this.sockIn = sockIn;216this.sockOut = sockOut;217input = sockIn.getInputStream();218output = sockOut.getOutputStream();219setDaemon(true);220}221222public void run() {223int BUFFER_SIZE = 400;224byte[] buf = new byte[BUFFER_SIZE];225int bytesRead = 0;226int count = 0; // keep track of the amount of data transfer227228try {229while ((bytesRead = input.read(buf)) >= 0) {230output.write(buf, 0, bytesRead);231output.flush();232count += bytesRead;233}234} catch (IOException e) {235/*236* The peer end has closed the connection237* we will close the tunnel238*/239close();240}241}242243public void close() {244try {245if (!sockIn.isClosed())246sockIn.close();247if (!sockOut.isClosed())248sockOut.close();249} catch (IOException ignored) { }250}251}252253/*254***************************************************************255* helper methods follow256***************************************************************257*/258259/*260* This method retrieves the hostname and port of the destination261* that the connect request wants to establish a tunnel for262* communication.263* The input, connectStr is of the form:264* CONNECT server-name:server-port HTTP/1.x265*/266private void retrieveConnectInfo(String connectStr) throws Exception {267268int starti;269int endi;270String connectInfo;271String serverName = null;272try {273starti = connectStr.indexOf(' ');274endi = connectStr.lastIndexOf(' ');275connectInfo = connectStr.substring(starti+1, endi).trim();276// retrieve server name and port277endi = connectInfo.indexOf(':');278serverName = connectInfo.substring(0, endi);279serverPort = Integer.parseInt(connectInfo.substring(endi+1));280} catch (Exception e) {281throw new IOException("Proxy received a request: "282+ connectStr, e);283}284serverInetAddr = InetAddress.getByName(serverName);285}286287public int getPort() {288return ss.getLocalPort();289}290291public InetAddress getInetAddress() {292return ss.getInetAddress();293}294295/*296* do "basic" authentication, authInfo is of the form:297* Basic <encoded username":"password>298* reference RFC 2617299*/300private boolean authenticate(String authInfo) throws IOException {301boolean matched = false;302try {303authInfo.trim();304int ind = authInfo.indexOf(' ');305String recvdUserPlusPass = authInfo.substring(ind + 1).trim();306307// extract encoded (username:passwd308if (userPlusPass.equals(309new String( Base64.getMimeDecoder()310.decode(recvdUserPlusPass))))311{312matched = true;313}314} catch (Exception e) {315throw new IOException(316"Proxy received invalid Proxy-Authorization value: "317+ authInfo);318}319return matched;320}321}322323324