Path: blob/master/test/jdk/sun/net/www/protocol/http/ProxyTunnelServer.java
41159 views
/*1* Copyright (c) 2002, 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* 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 final ServerSocket ss;41/*42* holds the registered user's username and password43* only one such entry is maintained44*/45private volatile String userPlusPass;4647// client requesting for a tunnel48private volatile Socket clientSocket = null;4950/*51* Origin server's address and port that the client52* wants to establish the tunnel for communication.53*/54private volatile InetAddress serverInetAddr;55private volatile int serverPort;5657/*58* denote whether the proxy needs to authorize59* CONNECT requests.60*/6162volatile boolean needAuth = false;6364public ProxyTunnelServer() throws IOException {65ss = new ServerSocket(0);66}6768public ProxyTunnelServer(InetAddress address) throws IOException {69ss = new ServerSocket(0, 0, address);70}7172static private void close(Closeable c) {73try {74if (c != null)75c.close();76} catch (IOException e) {77e.printStackTrace();78}79}8081public void needUserAuth(boolean auth) {82needAuth = auth;83}8485public void terminate() {86close(ss);87close(clientSocket);88}8990/*91* register users with the proxy, by providing username and92* password. The username and password are used for authorizing the93* user when a CONNECT request is made and needAuth is set to true.94*/95public void setUserAuth(String uname, String passwd) {96userPlusPass = uname + ":" + passwd;97}9899volatile boolean makeTunnel;100101public void doTunnel(boolean tunnel) {102makeTunnel = tunnel;103}104105public void run() {106try {107clientSocket = ss.accept();108processRequests(makeTunnel);109} catch (Exception e) {110System.out.println("Proxy Failed: " + e);111e.printStackTrace();112try {113ss.close();114}115catch (IOException excep) {116System.out.println("ProxyServer close error: " + excep);117excep.printStackTrace();118}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(boolean makeTunnel) throws Exception {130InputStream in = clientSocket.getInputStream();131MessageHeader mheader = new MessageHeader(in);132String statusLine = mheader.getValue(0);133134System.out.printf("Proxy: Processing request from '%s'%n", clientSocket);135136if (statusLine.startsWith("CONNECT")) {137// retrieve the host and port info from the status-line138// retrieveConnectInfo(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}150151if (makeTunnel) {152retrieveConnectInfo(statusLine);153doTunnel();154return;155}156157respondForConnect(needAuth);158159// connection set to the tunneling mode160if (!needAuth) {161// doTunnel();162/*163* done with tunneling, we process only one successful164* tunneling request165*/166ss.close();167} else {168// we may get another request with Proxy-Authorization set169in.close();170clientSocket.close();171restart();172}173} else {174System.out.println("proxy server: processes only "175+ "CONNECT method requests, recieved: "176+ statusLine);177}178}179180private void respondForConnect(boolean needAuth) throws Exception {181182OutputStream out = clientSocket.getOutputStream();183PrintWriter pout = new PrintWriter(out);184185if (needAuth) {186pout.println("HTTP/1.1 407 Proxy Auth Required");187pout.println("Proxy-Authenticate: Basic realm=\"WallyWorld\"");188pout.println();189pout.flush();190out.close();191} else {192pout.println("HTTP/1.1 500 Server Error");193pout.println();194pout.flush();195out.close();196}197}198199private void restart() throws IOException {200(new Thread(this)).start();201}202203/*sc204* note: Tunneling has to be provided in both directions, i.e205* from client->server and server->client, even if the application206* data may be unidirectional, SSL handshaking data flows in either207* direction.208*/209private void doTunnel() throws Exception {210OutputStream out = clientSocket.getOutputStream();211out.write("HTTP/1.1 200 OK\r\n\r\n".getBytes());212out.flush();213214Socket serverSocket = new Socket(serverInetAddr, serverPort);215ProxyTunnel clientToServer = new ProxyTunnel(216clientSocket, serverSocket);217ProxyTunnel serverToClient = new ProxyTunnel(218serverSocket, clientSocket);219clientToServer.start();220serverToClient.start();221System.out.println("Proxy: Started tunneling.......");222223clientToServer.join();224serverToClient.join();225System.out.println("Proxy: Finished tunneling........");226227clientToServer.close();228serverToClient.close();229}230231/*232* This inner class provides unidirectional data flow through the sockets233* by continuously copying bytes from the input socket onto the output234* socket, until both sockets are open and EOF has not been received.235*/236class ProxyTunnel extends Thread {237final Socket sockIn;238final Socket sockOut;239final InputStream input;240final OutputStream output;241242public ProxyTunnel(Socket sockIn, Socket sockOut)243throws Exception {244this.sockIn = sockIn;245this.sockOut = sockOut;246input = sockIn.getInputStream();247output = sockOut.getOutputStream();248}249250public void run() {251int BUFFER_SIZE = 400;252byte[] buf = new byte[BUFFER_SIZE];253int bytesRead = 0;254int count = 0; // keep track of the amount of data transfer255256try {257while ((bytesRead = input.read(buf)) >= 0) {258output.write(buf, 0, bytesRead);259output.flush();260count += bytesRead;261}262} catch (IOException e) {263/*264* The peer end has closed the connection265* we will close the tunnel266*/267close();268}269}270271public void close() {272try {273if (!sockIn.isClosed())274sockIn.close();275if (!sockOut.isClosed())276sockOut.close();277} catch (IOException ignored) { }278}279}280281/*282***************************************************************283* helper methods follow284***************************************************************285*/286287/*288* This method retrieves the hostname and port of the destination289* that the connect request wants to establish a tunnel for290* communication.291* The input, connectStr is of the form:292* CONNECT server-name:server-port HTTP/1.x293*/294private void retrieveConnectInfo(String connectStr) throws Exception {295int starti;296int endi;297String connectInfo;298String serverName = null;299try {300starti = connectStr.indexOf(' ');301endi = connectStr.lastIndexOf(' ');302connectInfo = connectStr.substring(starti+1, endi).trim();303// retrieve server name and port304endi = connectInfo.indexOf(':');305serverName = connectInfo.substring(0, endi);306serverPort = Integer.parseInt(connectInfo.substring(endi+1));307} catch (Exception e) {308throw new IOException("Proxy recieved a request: "309+ connectStr);310}311serverInetAddr = InetAddress.getByName(serverName);312}313314public int getPort() {315return ss.getLocalPort();316}317318public InetAddress getInetAddress() {319return ss.getInetAddress();320}321322/*323* do "basic" authentication, authInfo is of the form:324* Basic <encoded username":"password>325* reference RFC 2617326*/327private boolean authenticate(String authInfo) throws IOException {328boolean matched = false;329try {330authInfo.trim();331int ind = authInfo.indexOf(' ');332String recvdUserPlusPass = authInfo.substring(ind + 1).trim();333// extract encoded (username:passwd334if (userPlusPass.equals(335new String(Base64.getDecoder().decode(recvdUserPlusPass))336)) {337matched = true;338}339} catch (Exception e) {340throw new IOException(341"Proxy received invalid Proxy-Authorization value: "342+ authInfo);343}344return matched;345}346}347348349