Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/test/sun/net/www/protocol/https/HttpsURLConnection/ProxyTunnelServer.java
38889 views
/*1* Copyright (c) 2001, 2016, 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;6465public ProxyTunnelServer() throws IOException {66if (ss == null) {67ss = (ServerSocket) ServerSocketFactory.getDefault()68.createServerSocket(0);69ss.setSoTimeout(TIMEOUT);70}71}7273public void needUserAuth(boolean auth) {74needAuth = auth;75}7677/*78* register users with the proxy, by providing username and79* password. The username and password are used for authorizing the80* user when a CONNECT request is made and needAuth is set to true.81*/82public void setUserAuth(String uname, String passwd) {83userPlusPass = uname + ":" + passwd;84}8586public void run() {87try {88clientSocket = ss.accept();89processRequests();90} catch (SocketTimeoutException e) {91System.out.println(92"Proxy can not get response in time: " + e.getMessage());93} catch (Exception e) {94System.out.println("Proxy Failed: " + e);95e.printStackTrace();96try {97ss.close();98}99catch (IOException excep) {100System.out.println("ProxyServer close error: " + excep);101excep.printStackTrace();102}103}104}105106/*107* Processes the CONNECT requests, if needAuth is set to true, then108* the name and password are extracted from the Proxy-Authorization header109* of the request. They are checked against the one that is registered,110* if there is a match, connection is set in tunneling mode. If111* needAuth is set to false, Proxy-Authorization checks are not made112*/113private void processRequests() throws Exception {114115InputStream in = clientSocket.getInputStream();116MessageHeader mheader = new MessageHeader(in);117String statusLine = mheader.getValue(0);118119if (statusLine.startsWith("CONNECT")) {120// retrieve the host and port info from the status-line121retrieveConnectInfo(statusLine);122if (needAuth) {123String authInfo;124if ((authInfo = mheader.findValue("Proxy-Authorization"))125!= null) {126if (authenticate(authInfo)) {127needAuth = false;128System.out.println(129"Proxy: client authentication successful");130}131}132}133respondForConnect(needAuth);134135// connection set to the tunneling mode136if (!needAuth) {137doTunnel();138/*139* done with tunneling, we process only one successful140* tunneling request141*/142ss.close();143} else {144// we may get another request with Proxy-Authorization set145in.close();146clientSocket.close();147restart();148}149} else {150System.out.println("proxy server: processes only "151+ "CONNECT method requests, recieved: "152+ statusLine);153}154}155156private void respondForConnect(boolean needAuth) throws Exception {157158OutputStream out = clientSocket.getOutputStream();159PrintWriter pout = new PrintWriter(out);160161if (needAuth) {162pout.println("HTTP/1.1 407 Proxy Auth Required");163pout.println("Proxy-Authenticate: Basic realm=\"WallyWorld\"");164pout.println();165pout.flush();166out.close();167} else {168pout.println("HTTP/1.1 200 OK");169pout.println();170pout.flush();171}172}173174private void restart() throws IOException {175(new Thread(this)).start();176}177178/*179* note: Tunneling has to be provided in both directions, i.e180* from client->server and server->client, even if the application181* data may be unidirectional, SSL handshaking data flows in either182* direction.183*/184private void doTunnel() throws Exception {185186Socket serverSocket = new Socket(serverInetAddr, serverPort);187ProxyTunnel clientToServer = new ProxyTunnel(188clientSocket, serverSocket);189ProxyTunnel serverToClient = new ProxyTunnel(190serverSocket, clientSocket);191clientToServer.start();192serverToClient.start();193System.out.println("Proxy: Started tunneling.......");194195clientToServer.join(TIMEOUT);196serverToClient.join(TIMEOUT);197System.out.println("Proxy: Finished tunneling........");198199clientToServer.close();200serverToClient.close();201}202203/*204* This inner class provides unidirectional data flow through the sockets205* by continuously copying bytes from the input socket onto the output206* socket, until both sockets are open and EOF has not been received.207*/208class ProxyTunnel extends Thread {209Socket sockIn;210Socket sockOut;211InputStream input;212OutputStream output;213214public ProxyTunnel(Socket sockIn, Socket sockOut)215throws Exception {216this.sockIn = sockIn;217this.sockOut = sockOut;218input = sockIn.getInputStream();219output = sockOut.getOutputStream();220}221222public void run() {223int BUFFER_SIZE = 400;224byte[] buf = new byte[BUFFER_SIZE];225int bytesRead = 0;226227try {228while ((bytesRead = input.read(buf)) >= 0) {229output.write(buf, 0, bytesRead);230output.flush();231}232} catch (IOException e) {233/*234* The peer end has closed the connection235* we will close the tunnel236*/237close();238}239}240241private void close() {242try {243if (!sockIn.isClosed())244sockIn.close();245if (!sockOut.isClosed())246sockOut.close();247} catch (IOException ignored) { }248}249}250251/*252***************************************************************253* helper methods follow254***************************************************************255*/256257/*258* This method retrieves the hostname and port of the destination259* that the connect request wants to establish a tunnel for260* communication.261* The input, connectStr is of the form:262* CONNECT server-name:server-port HTTP/1.x263*/264private void retrieveConnectInfo(String connectStr) throws Exception {265266int starti;267int endi;268String connectInfo;269String serverName = null;270try {271starti = connectStr.indexOf(' ');272endi = connectStr.lastIndexOf(' ');273connectInfo = connectStr.substring(starti+1, endi).trim();274// retrieve server name and port275endi = connectInfo.indexOf(':');276serverName = connectInfo.substring(0, endi);277serverPort = Integer.parseInt(connectInfo.substring(endi+1));278} catch (Exception e) {279throw new IOException("Proxy recieved a request: "280+ connectStr, e);281}282serverInetAddr = InetAddress.getByName(serverName);283}284285public int getPort() {286return ss.getLocalPort();287}288289/*290* do "basic" authentication, authInfo is of the form:291* Basic <encoded username":"password>292* reference RFC 2617293*/294private boolean authenticate(String authInfo) throws IOException {295boolean matched = false;296try {297authInfo.trim();298int ind = authInfo.indexOf(' ');299String recvdUserPlusPass = authInfo.substring(ind + 1).trim();300// extract encoded (username:passwd301if (userPlusPass.equals(302new String( Base64.getMimeDecoder()303.decode(recvdUserPlusPass))))304{305matched = true;306}307} catch (Exception e) {308throw new IOException(309"Proxy received invalid Proxy-Authorization value: "310+ authInfo);311}312return matched;313}314}315316317