package simplejavaproxy; /* * This is a simple multi-threaded Java proxy server * for HTTP requests (HTTPS doesn't seem to work, because * the CONNECT requests aren't always handled properly). * I implemented the class as a thread so you can call it * from other programs and kill it, if necessary (by using * the closeSocket() method). * * We'll call this the 1.1 version of this class. All I * changed was to separate the HTTP header elements with * \r\n instead of just \n, to comply with the official * HTTP specification. * * This can be used either as a direct proxy to other * servers, or as a forwarding proxy to another proxy * server. This makes it useful if you want to monitor * traffic going to and from a proxy server (for example, * you can run this on your local machine and set the * fwdServer and fwdPort to a real proxy server, and then * tell your browser to use "localhost" as the proxy, and * you can watch the browser traffic going in and out). * * One limitation of this implementation is that it doesn't * close the ProxyThread socket if the client disconnects * or the server never responds, so you could end up with * a bunch of loose threads running amuck and waiting for * connections. As a band-aid, you can set the server socket * to timeout after a certain amount of time (use the * setTimeout() method in the ProxyThread class), although * this can cause false timeouts if a remote server is simply * slow to respond. * * Another thing is that it doesn't limit the number of * socket threads it will create, so if you use this on a * really busy machine that processed a bunch of requests, * you may have problems. You should use thread pools if * you're going to try something like this in a "real" * application. * * Note that if you're using the "main" method to run this * by itself and you don't need the debug output, it will * run a bit faster if you pipe the std output to 'nul'. * * You may use this code as you wish, just don't pretend * that you wrote it yourself, and don't hold me liable for * anything that it does or doesn't do. If you're feeling * especially honest, please include a link to nsftools.com * along with the code. Thanks, and good luck. * * Julian Robichaux -- http://www.nsftools.com */ import java.io.*; import java.net.*; import java.lang.reflect.Array; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class SimpleProxy extends Thread { public static final int DEFAULT_PORT = 8080; private ServerSocket server = null; private int thisPort = DEFAULT_PORT; private String fwdServer = ""; private int fwdPort = 0; private int ptTimeout = ProxyThread.DEFAULT_TIMEOUT; private int debugLevel = 1; private PrintStream debugOut = System.out; //FMG>> public static String safariKey = ""; public static Map cookieMap = new ConcurrentHashMap(); /* here's a main method, in case you want to run this by itself */ public static void main(String args[]) { int port = 0; String fwdProxyServer = ""; int fwdProxyPort = 0; if (args.length == 0) { System.err.println("USAGE: java jProxy [ ]"); System.err.println(" the port this service listens on"); System.err.println(" optional proxy server to forward requests to"); System.err.println(" the port that the optional proxy server is on"); System.err.println("\nHINT: if you don't want to see all the debug information flying by,"); System.err.println("you can pipe the output to a file or to 'nul' using \">\". For example:"); System.err.println(" to send output to the file prox.txt: java jProxy 8080 > prox.txt"); System.err.println(" to make the output go away: java jProxy 8080 > nul"); return; } // get the command-line parameters port = Integer.parseInt(args[0]); if (args.length > 2) { fwdProxyServer = args[1]; fwdProxyPort = Integer.parseInt(args[2]); } // create and start the jProxy thread, using a 20 second timeout // value to keep the threads from piling up too much System.err.println(" ** Starting jProxy on port " + port + ". Press CTRL-C to end. **\n"); SimpleProxy jp = new SimpleProxy(port, fwdProxyServer, fwdProxyPort, 20); jp.setDebug(1, System.out); // or set the debug level to 2 for tons of output jp.start(); // run forever; if you were calling this class from another // program and you wanted to stop the jProxy thread at some // point, you could write a loop that waits for a certain // condition and then calls jProxy.closeSocket() to kill // the running jProxy thread while (true) { try { Thread.sleep(3000); } catch (Exception e) { } } // if we ever had a condition that stopped the loop above, // we'd want to do this to kill the running thread //jp.closeSocket(); //return; } /* the proxy server just listens for connections and creates * a new thread for each connection attempt (the ProxyThread * class really does all the work) */ public SimpleProxy(int port) { thisPort = port; } public SimpleProxy(int port, String proxyServer, int proxyPort) { thisPort = port; fwdServer = proxyServer; fwdPort = proxyPort; } public SimpleProxy(int port, String proxyServer, int proxyPort, int timeout) { thisPort = port; fwdServer = proxyServer; fwdPort = proxyPort; ptTimeout = timeout; } /* allow the user to decide whether or not to send debug * output to the console or some other PrintStream */ public void setDebug(int level, PrintStream out) { debugLevel = level; debugOut = out; } /* get the port that we're supposed to be listening on */ public int getPort() { return thisPort; } /* return whether or not the socket is currently open */ public boolean isRunning() { if (server == null) { return false; } else { return true; } } /* closeSocket will close the open ServerSocket; use this * to halt a running jProxy thread */ public void closeSocket() { try { // close the open server socket server.close(); // send it a message to make it stop waiting immediately // (not really necessary) /*Socket s = new Socket("localhost", thisPort); OutputStream os = s.getOutputStream(); os.write((byte)0); os.close(); s.close();*/ } catch (Exception e) { if (debugLevel > 0) { debugOut.println(e); } } server = null; } public void run() { try { // create a server socket, and loop forever listening for // client connections server = new ServerSocket(thisPort); if (debugLevel > 0) { debugOut.println("Started jProxy on port " + thisPort); } while (true) { Socket client = server.accept(); ProxyThread t = new ProxyThread(client, fwdServer, fwdPort); t.setDebug(debugLevel, debugOut); t.setTimeout(ptTimeout); t.start(); } } catch (Exception e) { if (debugLevel > 0) { debugOut.println("jProxy Thread error: " + e); } } closeSocket(); } } /* * The ProxyThread will take an HTTP request from the client * socket and send it to either the server that the client is * trying to contact, or another proxy server */ class ProxyThread extends Thread { private Socket pSocket; private String fwdServer = ""; private int fwdPort = 0; private int debugLevel = 0; private PrintStream debugOut = System.out; // the socketTimeout is used to time out the connection to // the remote server after a certain period of inactivity; // the value is in milliseconds -- use zero if you don't want // a timeout public static final int DEFAULT_TIMEOUT = 20 * 1000; private int socketTimeout = DEFAULT_TIMEOUT; public ProxyThread(Socket s) { pSocket = s; } public ProxyThread(Socket s, String proxy, int port) { pSocket = s; fwdServer = proxy; fwdPort = port; } public void setTimeout(int timeout) { // assume that the user will pass the timeout value // in seconds (because that's just more intuitive) socketTimeout = timeout * 1000; } public void setDebug(int level, PrintStream out) { debugLevel = level; debugOut = out; } public void run() { try { long startTime = System.currentTimeMillis(); // client streams (make sure you're using streams that use // byte arrays, so things like GIF and JPEG files and file // downloads will transfer properly) BufferedInputStream clientIn = new BufferedInputStream(pSocket.getInputStream()); BufferedOutputStream clientOut = new BufferedOutputStream(pSocket.getOutputStream()); // the socket to the remote server Socket server = null; // other variables byte[] request = null; byte[] response = null; int requestLength = 0; int responseLength = 0; int pos = -1; StringBuffer host = new StringBuffer(""); String hostName = ""; int hostPort = 80; // get the header info (the web browser won't disconnect after // it's sent a request, so make sure the waitForDisconnect // parameter is false) request = getHTTPData(clientIn, host, false); requestLength = Array.getLength(request); // separate the host name from the host port, if necessary // (like if it's "servername:8000") hostName = host.toString(); pos = hostName.indexOf(":"); if (pos > 0) { try { hostPort = Integer.parseInt(hostName.substring(pos + 1)); } catch (Exception e) { } hostName = hostName.substring(0, pos); } // either forward this request to another proxy server or // send it straight to the Host try { if ((fwdServer.length() > 0) && (fwdPort > 0)) { server = new Socket(fwdServer, fwdPort); } else { server = new Socket(hostName, hostPort); } } catch (Exception e) { // tell the client there was an error String errMsg = "HTTP/1.0 500\nContent Type: text/plain\n\n" + "Error connecting to the server:\n" + e + "\n"; clientOut.write(errMsg.getBytes(), 0, errMsg.length()); } if (server != null) { server.setSoTimeout(socketTimeout); BufferedInputStream serverIn = new BufferedInputStream(server.getInputStream()); BufferedOutputStream serverOut = new BufferedOutputStream(server.getOutputStream()); // send the request out serverOut.write(request, 0, requestLength); serverOut.flush(); // and get the response; if we're not at a debug level that // requires us to return the data in the response, just stream // it back to the client to save ourselves from having to // create and destroy an unnecessary byte array. Also, we // should set the waitForDisconnect parameter to 'true', // because some servers (like Google) don't always set the // Content-Length header field, so we have to listen until // they decide to disconnect (or the connection times out). if (debugLevel > 1) { response = getHTTPData(serverIn, true); responseLength = Array.getLength(response); } else { responseLength = streamHTTPData(serverIn, clientOut, true); } serverIn.close(); serverOut.close(); } // send the response back to the client, if we haven't already if (debugLevel > 1) { clientOut.write(response, 0, responseLength); } // if the user wants debug info, send them debug info; however, // keep in mind that because we're using threads, the output won't // necessarily be synchronous if (debugLevel > 5) { long endTime = System.currentTimeMillis(); debugOut.println("Request from " + pSocket.getInetAddress().getHostAddress() + " on Port " + pSocket.getLocalPort() + " to host " + hostName + ":" + hostPort + "\n (" + requestLength + " bytes sent, " + responseLength + " bytes returned, " + Long.toString(endTime - startTime) + " ms elapsed)"); debugOut.flush(); } if (debugLevel > 10) { debugOut.println("REQUEST:\n" + (new String(request))); debugOut.println("RESPONSE:\n" + (new String(response))); debugOut.flush(); } // close all the client streams so we can listen again clientOut.close(); clientIn.close(); pSocket.close(); } catch (Exception e) { if (debugLevel > 0) { debugOut.println("Error in ProxyThread: " + e); } //e.printStackTrace(); } } private byte[] getHTTPData(InputStream in, boolean waitForDisconnect) { // get the HTTP data from an InputStream, and return it as // a byte array // the waitForDisconnect parameter tells us what to do in case // the HTTP header doesn't specify the Content-Length of the // transmission StringBuffer foo = new StringBuffer(""); return getHTTPData(in, foo, waitForDisconnect); } private byte[] getHTTPData(InputStream in, StringBuffer host, boolean waitForDisconnect) { // get the HTTP data from an InputStream, and return it as // a byte array, and also return the Host entry in the header, // if it's specified -- note that we have to use a StringBuffer // for the 'host' variable, because a String won't return any // information when it's used as a parameter like that ByteArrayOutputStream bs = new ByteArrayOutputStream(); streamHTTPData(in, bs, host, waitForDisconnect); return bs.toByteArray(); } private int streamHTTPData(InputStream in, OutputStream out, boolean waitForDisconnect) { StringBuffer foo = new StringBuffer(""); return streamHTTPData(in, out, foo, waitForDisconnect); } private int streamHTTPData(InputStream in, OutputStream out, StringBuffer host, boolean waitForDisconnect) { // get the HTTP data from an InputStream, and send it to // the designated OutputStream StringBuffer header = new StringBuffer(""); String data = ""; int responseCode = 200; int contentLength = 0; int pos = -1; int byteCount = 0; String originalCookies = null; try { // get the first line of the header, so we know the response code data = readLine(in); if (data != null) { header.append(data + "\r\n"); pos = data.indexOf(" "); if ((data.toLowerCase().startsWith("http")) && (pos >= 0) && (data.indexOf(" ", pos + 1) >= 0)) { String rcString = data.substring(pos + 1, data.indexOf(" ", pos + 1)); try { responseCode = Integer.parseInt(rcString); } catch (Exception e) { if (debugLevel > 0) { debugOut.println("Error parsing response code " + rcString); } } } } // get the rest of the header info while ((data = readLine(in)) != null) { // the header ends at the first blank line if (data.length() == 0) { break; } //header.append(data + "\r\n"); //FMG commentlendi, cookie değiştireceğimiz için aşağıya taşıyorum // check for the Host header pos = data.toLowerCase().indexOf("host:"); if (pos >= 0) { host.setLength(0); host.append(data.substring(pos + 5).trim()); } // check for the Content-Length header pos = data.toLowerCase().indexOf("content-length:"); if (pos >= 0) { contentLength = Integer.parseInt(data.substring(pos + 15).trim()); } //************************************************************** //FMG>>: simulate browser (User Agent) cookie behaviour //We want the server beleive that we are a single browser. //Set-Cookie: Safari=cookieversion=2&portal=my&key=E894F897E4A4912E6D683D6939BE74D192731C8F4191A3ACAB6B3FEE16588D2954DBF3B9BF495294EE68A4477E3D772EAA5B0E89433D9926603960942389D153574A4D8BAB4095687832999AECDC2C751104CB8D1A30141A9BCD769E3A126390FFDD02DA9C28307232CD000C64D4F0C3D2993A357CCBED2A19E0467900E3884D71&sessionid=1a923806-a923-4442-9579-325c77ae39db&ref=Google&logged=true&oref=http%3a%2f%2fwww.google.com.tr%2furl%3fsa%3dt%26source%3dweb%26ct%3dres%26cd%3d1%26ved%3d0CAYQFjAA%26url%3dhttp%253A%252F%252Fmy.safaribooksonline.com%252F%26rct%3dj%26q%3dsafari%2bbook%26ei%3d2ZHNS_XcBIX0ObjplcwP%26usg%3dAFQjCNFeqvBpwDvMtcrCBnfHPPiUQofzPA; Path=/; Domain=my.safaribooksonline.com //Cookie: __utmv=77706120.B2C-BS-10-M-X; __utma=77706120.1457821667.1270626763.1271763865.1271767431.4; __utmb=77706120; __utmz=77706120.1271763865.3.2.utmccn=(organic)|utmcsr=google|utmctr=safari+book|utmcmd=organic; Safari=cookieversion=2&portal=my&key=E894F897E4A4912E6D683D6939BE74D192731C8F4191A3ACAB6B3FEE16588D2954DBF3B9BF495294EE68A4477E3D772EAA5B0E89433D9926603960942389D153574A4D8BAB4095687832999AECDC2C751104CB8D1A30141A9BCD769E3A126390FFDD02DA9C28307232CD000C64D4F0C3D2993A357CCBED2A19E0467900E3884D71&sessionid=1a923806-a923-4442-9579-325c77ae39db&ref=Google&logged=true&oref=http%3a%2f%2fwww.google.com.tr%2furl%3fsa%3dt%26source%3dweb%26ct%3dres%26cd%3d1%26ved%3d0CAYQFjAA%26url%3dhttp%253A%252F%252Fmy.safaribooksonline.com%252F%26rct%3dj%26q%3dsafari%2bbook%26ei%3d2ZHNS_XcBIX0ObjplcwP%26usg%3dAFQjCNFeqvBpwDvMtcrCBnfHPPiUQofzPA; __utmc=77706120; State=aph=LGOjKGanGVMDHURjGSHANHTCDRCFICGGFLNRPYPXOXVNCCJKAAWOeKHTXMIS&uicode=&PromoCode=my_promo_10&itemsperpage=10&savekey=CB68946AF5AC267A97B7C73B7F8A341F83A137D7B0C6094366303123865F960DD622F95860088400226D1FB16FFCF62ED34A33EFF50B6CC3&HttpReferrer=http%3a%2f%2fmy.safaribooksonline.com%2f0201835959&reader=&action=20&search=safari+book&view=book&displaygrbooks=1&xmlid=0201835959%2f5&isbn=0201835959&omniturexmlidown=1&imagepage=5&portal=my; s_cc=true; s_sq=%5B%5BB%5D%5D // check for the Set-Cookie header pos = data.toLowerCase().indexOf("set-cookie:"); if (pos >= 0) { String setCookie = data.substring(pos + 11).trim(); handleSetCookie(setCookie); } //YAP: cookie headerını iptal et, aşağıda biz gönderiyoruz çünkü pos = data.toLowerCase().indexOf("cookie:"); if (pos >= 0) { //we do not send original cookies originalCookies = data.substring(pos + 7); } else { header.append(data + "\r\n"); } //<> //lastly send cookie if applicable for this host String cookie = getCookiesForHost(host.toString()); if (cookie != null) header.append(cookie + "\r\n"); if (originalCookies != null) System.out.println("\t _ookie:" + originalCookies); if (cookie == null && originalCookies != null) header.append("Cookie: " + originalCookies + "\r\n"); //< 0) { waitForDisconnect = false; } if ((contentLength > 0) || (waitForDisconnect)) { try { byte[] buf = new byte[4096]; int bytesIn = 0; while (((byteCount < contentLength) || (waitForDisconnect)) && ((bytesIn = in.read(buf)) >= 0)) { out.write(buf, 0, bytesIn); byteCount += bytesIn; } } catch (Exception e) { String errMsg = "Error getting HTTP body: " + e; if (debugLevel > 0) { debugOut.println(errMsg); } //bs.write(errMsg.getBytes(), 0, errMsg.length()); } } } catch (Exception e) { if (debugLevel > 0) { debugOut.println("Error getting HTTP data: " + e); } e.printStackTrace(); } //flush the OutputStream and return try { out.flush(); } catch (Exception e) { } return (header.length() + byteCount); } private String readLine(InputStream in) { // reads a line of text from an InputStream StringBuffer data = new StringBuffer(""); int c; try { // if we have nothing to read, just return null in.mark(1); if (in.read() == -1) { return null; } else { in.reset(); } while ((c = in.read()) >= 0) { // check for an end-of-line character if ((c == 0) || (c == 10) || (c == 13)) { break; } else { data.append((char)c); } } // deal with the case where the end-of-line terminator is \r\n if (c == 13) { in.mark(1); if (in.read() != 10) { in.reset(); } } } catch (Exception e) { if (debugLevel > 0) { debugOut.println("Error getting header: " + e); } e.printStackTrace(); } // and return what we have return data.toString(); } //FMG>> //We are simulating the User Agent role as in RFC2965 //the cookies coming from server, for a host private void handleSetCookie(String setCookie) { //global cookie map for each domain Map cookieMap = SimpleProxy.cookieMap; //parsed attribute value pair for setCookie (we should not send Domain, Path, Expires cookies back to server, so remove them) Map cookieValues = parseCookies(setCookie); String domain = (String)cookieValues.remove("Domain"); if (domain == null) domain = (String)cookieValues.remove("domain"); if (domain == null) domain = ""; cookieValues.remove("Path"); cookieValues.remove("Expires"); cookieValues.remove("path"); cookieValues.remove("expires"); //get cookies for this domain Map cookiesForDomain = (Map)cookieMap.get(domain); //create if not exists if (cookiesForDomain == null) cookiesForDomain = new ConcurrentHashMap(); //put & override the existing values cookiesForDomain.putAll(cookieValues); //set cookies for this host cookieMap.put(domain, cookiesForDomain); if (debugLevel > 0) { System.out.println(">>>> Got new cookie for domain: '" + domain); Iterator it = cookiesForDomain.keySet().iterator(); while (it.hasNext()) { String key = (String)it.next(); System.out.print("\tKey: " + key); System.out.println(" Value: " + cookiesForDomain.get(key)); } } } private Map parseCookies(String cookies) { Map result = new HashMap(); String[] cookieArray = cookies.split(";"); for (int i = 0; i < cookieArray.length; i++) { String attributeValue = cookieArray[i]; int eqIndex = attributeValue.indexOf("="); if (eqIndex == -1) continue; String attribute = attributeValue.substring(0, eqIndex); String value = attributeValue.substring(eqIndex + 1); result.put(attribute.trim(), value.trim()); } return result; } //returns the cookies to be sent for a host private String getCookiesForHost(String host) { //global cookie map for each domain Map cookieMap = SimpleProxy.cookieMap; //get cookies for this domain Map cookiesForDomain = (Map)cookieMap.get(host); if (cookiesForDomain == null) return null; StringBuffer cookie = new StringBuffer(); cookie.append("Cookie: "); Iterator it = cookiesForDomain.keySet().iterator(); while (it.hasNext()) { String key = (String)it.next(); cookie.append(key); cookie.append("="); cookie.append(cookiesForDomain.get(key)); if (it.hasNext()) cookie.append(";"); } if (debugLevel > 0) { System.out.println("Sending cookie for host: " + host); System.out.println("\t " + cookie.toString()); } return cookie.toString(); } }