Path: blob/master/test/jdk/sun/net/www/protocol/https/HttpsURLConnection/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* request is accepted.28* The proxy server processes only one transaction, i.e a CONNECT request29* followed by the corresponding tunnel data.30*/3132import java.io.*;33import java.net.*;34import javax.net.ServerSocketFactory;35import sun.net.www.*;36import java.util.Base64;3738public class ProxyTunnelServer extends Thread {3940private static final int TIMEOUT = 30000;4142private static ServerSocket ss = null;43/*44* holds the registered user's username and password45* only one such entry is maintained46*/47private String userPlusPass;4849// client requesting for a tunnel50private Socket clientSocket = null;5152/*53* Origin server's address and port that the client54* wants to establish the tunnel for communication.55*/56private InetAddress serverInetAddr;57private int serverPort;5859/*60* denote whether the proxy needs to authorize61* CONNECT requests.62*/63static boolean needAuth = false;6465volatile long connectCount;6667public ProxyTunnelServer() throws IOException {68if (ss == null) {69ss = (ServerSocket) ServerSocketFactory.getDefault()70.createServerSocket(0);71ss.setSoTimeout(TIMEOUT);72System.out.println("Proxy server created: " + ss);73}74}7576public ProxyTunnelServer(InetAddress address) throws IOException {77if (ss == null) {78ss = (ServerSocket) ServerSocketFactory.getDefault()79.createServerSocket(0, 0, address);80ss.setSoTimeout(TIMEOUT);81System.out.println("Proxy server created: " + ss);82}83}8485public void needUserAuth(boolean auth) {86needAuth = auth;87}8889/*90* register users with the proxy, by providing username and91* password. The username and password are used for authorizing the92* user when a CONNECT request is made and needAuth is set to true.93*/94public void setUserAuth(String uname, String passwd) {95userPlusPass = uname + ":" + passwd;96}9798public void run() {99try {100System.out.println("Proxy server listening at: " + ss);101clientSocket = ss.accept();102System.out.println("Proxy server accepted connection: " + clientSocket);103processRequests();104} catch (SocketTimeoutException e) {105System.out.println(106"Proxy can not get response in time: " + e.getMessage());107} catch (Exception e) {108System.out.println("Proxy Failed: " + e);109e.printStackTrace();110try {111ss.close();112}113catch (IOException excep) {114System.out.println("ProxyServer close error: " + excep);115excep.printStackTrace();116}117} finally {118System.out.println("Proxy server: request served");119}120}121122/*123* Processes the CONNECT requests, if needAuth is set to true, then124* the name and password are extracted from the Proxy-Authorization header125* of the request. They are checked against the one that is registered,126* if there is a match, connection is set in tunneling mode. If127* needAuth is set to false, Proxy-Authorization checks are not made128*/129private void processRequests() throws Exception {130131InputStream in = clientSocket.getInputStream();132MessageHeader mheader = new MessageHeader(in);133String statusLine = mheader.getValue(0);134135if (statusLine.startsWith("CONNECT")) {136synchronized(this) { connectCount++; }137// retrieve the host and port info from the status-line138retrieveConnectInfo(statusLine);139if (needAuth) {140String authInfo;141if ((authInfo = mheader.findValue("Proxy-Authorization"))142!= null) {143if (authenticate(authInfo)) {144needAuth = false;145System.out.println(146"Proxy: client authentication successful");147}148}149}150respondForConnect(needAuth);151152// connection set to the tunneling mode153if (!needAuth) {154doTunnel();155/*156* done with tunneling, we process only one successful157* tunneling request158*/159ss.close();160} else {161// we may get another request with Proxy-Authorization set162in.close();163clientSocket.close();164restart();165}166} else {167System.out.println("proxy server: processes only "168+ "CONNECT method requests, recieved: "169+ statusLine);170}171}172173public long getConnectCount() {174return connectCount;175}176177private void respondForConnect(boolean needAuth) throws Exception {178179OutputStream out = clientSocket.getOutputStream();180PrintWriter pout = new PrintWriter(out);181182if (needAuth) {183pout.println("HTTP/1.1 407 Proxy Auth Required");184pout.println("Proxy-Authenticate: Basic realm=\"WallyWorld\"");185pout.println();186pout.flush();187out.close();188} else {189pout.println("HTTP/1.1 200 OK");190pout.println();191pout.flush();192}193}194195private void restart() throws IOException {196(new Thread(this)).start();197}198199/*200* note: Tunneling has to be provided in both directions, i.e201* from client->server and server->client, even if the application202* data may be unidirectional, SSL handshaking data flows in either203* direction.204*/205private void doTunnel() throws Exception {206207Socket serverSocket = new Socket(serverInetAddr, serverPort);208ProxyTunnel clientToServer = new ProxyTunnel(209clientSocket, serverSocket);210ProxyTunnel serverToClient = new ProxyTunnel(211serverSocket, clientSocket);212clientToServer.start();213serverToClient.start();214System.out.println("Proxy: Started tunneling.......");215216clientToServer.join(TIMEOUT);217serverToClient.join(TIMEOUT);218System.out.println("Proxy: Finished tunneling........");219220clientToServer.close();221serverToClient.close();222}223224/*225* This inner class provides unidirectional data flow through the sockets226* by continuously copying bytes from the input socket onto the output227* socket, until both sockets are open and EOF has not been received.228*/229class ProxyTunnel extends Thread {230Socket sockIn;231Socket sockOut;232InputStream input;233OutputStream output;234235public ProxyTunnel(Socket sockIn, Socket sockOut)236throws Exception {237this.sockIn = sockIn;238this.sockOut = sockOut;239input = sockIn.getInputStream();240output = sockOut.getOutputStream();241}242243public void run() {244int BUFFER_SIZE = 400;245byte[] buf = new byte[BUFFER_SIZE];246int bytesRead = 0;247248try {249while ((bytesRead = input.read(buf)) >= 0) {250output.write(buf, 0, bytesRead);251output.flush();252}253} catch (IOException e) {254/*255* The peer end has closed the connection256* we will close the tunnel257*/258close();259}260}261262private void close() {263try {264if (!sockIn.isClosed())265sockIn.close();266if (!sockOut.isClosed())267sockOut.close();268} catch (IOException ignored) { }269}270}271272/*273***************************************************************274* helper methods follow275***************************************************************276*/277278/*279* This method retrieves the hostname and port of the destination280* that the connect request wants to establish a tunnel for281* communication.282* The input, connectStr is of the form:283* CONNECT server-name:server-port HTTP/1.x284*/285private void retrieveConnectInfo(String connectStr) throws Exception {286287int starti;288int endi;289String connectInfo;290String serverName = null;291try {292starti = connectStr.indexOf(' ');293endi = connectStr.lastIndexOf(' ');294connectInfo = connectStr.substring(starti+1, endi).trim();295// retrieve server name and port296if (connectInfo.charAt(0) == '[') {297endi = connectInfo.indexOf(']');298serverName = connectInfo.substring(1, endi++);299assert connectInfo.charAt(endi) == ':' : "Expected [IPv6]:port";300} else {301endi = connectInfo.indexOf(':');302serverName = connectInfo.substring(0, endi);303}304serverPort = Integer.parseInt(connectInfo.substring(endi+1));305} catch (Exception e) {306throw new IOException("Proxy recieved a request: "307+ connectStr, e);308}309serverInetAddr = InetAddress.getByName(serverName);310}311312public int getPort() {313return ss.getLocalPort();314}315316/*317* do "basic" authentication, authInfo is of the form:318* Basic <encoded username":"password>319* reference RFC 2617320*/321private boolean authenticate(String authInfo) throws IOException {322boolean matched = false;323try {324authInfo.trim();325int ind = authInfo.indexOf(' ');326String recvdUserPlusPass = authInfo.substring(ind + 1).trim();327// extract encoded (username:passwd328if (userPlusPass.equals(329new String( Base64.getMimeDecoder()330.decode(recvdUserPlusPass))))331{332matched = true;333}334} catch (Exception e) {335throw new IOException(336"Proxy received invalid Proxy-Authorization value: "337+ authInfo);338}339return matched;340}341}342343344