source: trunk/SimpleJavaProxy/src/simplejavaproxy/SimpleProxy.java @ 4

Last change on this file since 4 was 4, checked in by fmguler, 14 years ago

SimpleJavaProxy? ilk import. Basit bir java proxy server. Tarayıcıdan bağımsız cookie'leri yönetmek için oluşturulan bir yardımcı proje. Bu halinde safari key'ini override ediyor.

File size: 25.1 KB
Line 
1package simplejavaproxy;
2
3/*
4 * This is a simple multi-threaded Java proxy server
5 * for HTTP requests (HTTPS doesn't seem to work, because
6 * the CONNECT requests aren't always handled properly).
7 * I implemented the class as a thread so you can call it
8 * from other programs and kill it, if necessary (by using
9 * the closeSocket() method).
10 *
11 * We'll call this the 1.1 version of this class. All I
12 * changed was to separate the HTTP header elements with
13 * \r\n instead of just \n, to comply with the official
14 * HTTP specification.
15 *
16 * This can be used either as a direct proxy to other
17 * servers, or as a forwarding proxy to another proxy
18 * server. This makes it useful if you want to monitor
19 * traffic going to and from a proxy server (for example,
20 * you can run this on your local machine and set the
21 * fwdServer and fwdPort to a real proxy server, and then
22 * tell your browser to use "localhost" as the proxy, and
23 * you can watch the browser traffic going in and out).
24 *
25 * One limitation of this implementation is that it doesn't
26 * close the ProxyThread socket if the client disconnects
27 * or the server never responds, so you could end up with
28 * a bunch of loose threads running amuck and waiting for
29 * connections. As a band-aid, you can set the server socket
30 * to timeout after a certain amount of time (use the
31 * setTimeout() method in the ProxyThread class), although
32 * this can cause false timeouts if a remote server is simply
33 * slow to respond.
34 *
35 * Another thing is that it doesn't limit the number of
36 * socket threads it will create, so if you use this on a
37 * really busy machine that processed a bunch of requests,
38 * you may have problems. You should use thread pools if
39 * you're going to try something like this in a "real"
40 * application.
41 *
42 * Note that if you're using the "main" method to run this
43 * by itself and you don't need the debug output, it will
44 * run a bit faster if you pipe the std output to 'nul'.
45 *
46 * You may use this code as you wish, just don't pretend
47 * that you wrote it yourself, and don't hold me liable for
48 * anything that it does or doesn't do. If you're feeling
49 * especially honest, please include a link to nsftools.com
50 * along with the code. Thanks, and good luck.
51 *
52 * Julian Robichaux -- http://www.nsftools.com
53 */
54import java.io.*;
55import java.net.*;
56import java.lang.reflect.Array;
57
58public class SimpleProxy extends Thread {
59
60    public static final int DEFAULT_PORT = 8080;
61    private ServerSocket server = null;
62    private int thisPort = DEFAULT_PORT;
63    private String fwdServer = "";
64    private int fwdPort = 0;
65    private int ptTimeout = ProxyThread.DEFAULT_TIMEOUT;
66    private int debugLevel = 0;
67    private PrintStream debugOut = System.out;
68    //FMG>>
69    public static String safariKey = "";
70
71
72    /* here's a main method, in case you want to run this by itself */
73    public static void main(String args[]) {
74        int port = 0;
75        String fwdProxyServer = "";
76        int fwdProxyPort = 0;
77
78        if (args.length == 0) {
79            System.err.println("USAGE: java jProxy <port number> [<fwd proxy> <fwd port>]");
80            System.err.println("  <port number>   the port this service listens on");
81            System.err.println("  <fwd proxy>     optional proxy server to forward requests to");
82            System.err.println("  <fwd port>      the port that the optional proxy server is on");
83            System.err.println("\nHINT: if you don't want to see all the debug information flying by,");
84            System.err.println("you can pipe the output to a file or to 'nul' using \">\". For example:");
85            System.err.println("  to send output to the file prox.txt: java jProxy 8080 > prox.txt");
86            System.err.println("  to make the output go away: java jProxy 8080 > nul");
87            return;
88        }
89
90        // get the command-line parameters
91        port = Integer.parseInt(args[0]);
92        if (args.length > 2) {
93            fwdProxyServer = args[1];
94            fwdProxyPort = Integer.parseInt(args[2]);
95        }
96
97        // create and start the jProxy thread, using a 20 second timeout
98        // value to keep the threads from piling up too much
99        System.err.println("  **  Starting jProxy on port " + port + ". Press CTRL-C to end.  **\n");
100        SimpleProxy jp = new SimpleProxy(port, fwdProxyServer, fwdProxyPort, 20);
101        jp.setDebug(1, System.out);             // or set the debug level to 2 for tons of output
102        jp.start();
103
104        // run forever; if you were calling this class from another
105        // program and you wanted to stop the jProxy thread at some
106        // point, you could write a loop that waits for a certain
107        // condition and then calls jProxy.closeSocket() to kill
108        // the running jProxy thread
109        while (true) {
110            try {
111                Thread.sleep(3000);
112            } catch (Exception e) {
113            }
114        }
115
116        // if we ever had a condition that stopped the loop above,
117        // we'd want to do this to kill the running thread
118        //jp.closeSocket();
119        //return;
120    }
121
122
123    /* the proxy server just listens for connections and creates
124     * a new thread for each connection attempt (the ProxyThread
125     * class really does all the work)
126     */
127    public SimpleProxy(int port) {
128        thisPort = port;
129    }
130
131    public SimpleProxy(int port, String proxyServer, int proxyPort) {
132        thisPort = port;
133        fwdServer = proxyServer;
134        fwdPort = proxyPort;
135    }
136
137    public SimpleProxy(int port, String proxyServer, int proxyPort, int timeout) {
138        thisPort = port;
139        fwdServer = proxyServer;
140        fwdPort = proxyPort;
141        ptTimeout = timeout;
142    }
143
144
145    /* allow the user to decide whether or not to send debug
146     * output to the console or some other PrintStream
147     */
148    public void setDebug(int level, PrintStream out) {
149        debugLevel = level;
150        debugOut = out;
151    }
152
153
154    /* get the port that we're supposed to be listening on
155     */
156    public int getPort() {
157        return thisPort;
158    }
159
160
161    /* return whether or not the socket is currently open
162     */
163    public boolean isRunning() {
164        if (server == null) {
165            return false;
166        } else {
167            return true;
168        }
169    }
170
171
172    /* closeSocket will close the open ServerSocket; use this
173     * to halt a running jProxy thread
174     */
175    public void closeSocket() {
176        try {
177            // close the open server socket
178            server.close();
179            // send it a message to make it stop waiting immediately
180            // (not really necessary)
181                        /*Socket s = new Socket("localhost", thisPort);
182            OutputStream os = s.getOutputStream();
183            os.write((byte)0);
184            os.close();
185            s.close();*/
186        } catch (Exception e) {
187            if (debugLevel > 0) {
188                debugOut.println(e);
189            }
190        }
191
192        server = null;
193    }
194
195    public void run() {
196        try {
197            // create a server socket, and loop forever listening for
198            // client connections
199            server = new ServerSocket(thisPort);
200            if (debugLevel > 0) {
201                debugOut.println("Started jProxy on port " + thisPort);
202            }
203
204            while (true) {
205                Socket client = server.accept();
206                ProxyThread t = new ProxyThread(client, fwdServer, fwdPort);
207                t.setDebug(debugLevel, debugOut);
208                t.setTimeout(ptTimeout);
209                t.start();
210            }
211        } catch (Exception e) {
212            if (debugLevel > 0) {
213                debugOut.println("jProxy Thread error: " + e);
214            }
215        }
216
217        closeSocket();
218    }
219}
220
221
222/*
223 * The ProxyThread will take an HTTP request from the client
224 * socket and send it to either the server that the client is
225 * trying to contact, or another proxy server
226 */
227class ProxyThread extends Thread {
228
229    private Socket pSocket;
230    private String fwdServer = "";
231    private int fwdPort = 0;
232    private int debugLevel = 0;
233    private PrintStream debugOut = System.out;
234    // the socketTimeout is used to time out the connection to
235    // the remote server after a certain period of inactivity;
236    // the value is in milliseconds -- use zero if you don't want
237    // a timeout
238    public static final int DEFAULT_TIMEOUT = 20 * 1000;
239    private int socketTimeout = DEFAULT_TIMEOUT;
240
241    public ProxyThread(Socket s) {
242        pSocket = s;
243    }
244
245    public ProxyThread(Socket s, String proxy, int port) {
246        pSocket = s;
247        fwdServer = proxy;
248        fwdPort = port;
249    }
250
251    public void setTimeout(int timeout) {
252        // assume that the user will pass the timeout value
253        // in seconds (because that's just more intuitive)
254        socketTimeout = timeout * 1000;
255    }
256
257    public void setDebug(int level, PrintStream out) {
258        debugLevel = level;
259        debugOut = out;
260    }
261
262    public void run() {
263        try {
264            long startTime = System.currentTimeMillis();
265
266            // client streams (make sure you're using streams that use
267            // byte arrays, so things like GIF and JPEG files and file
268            // downloads will transfer properly)
269            BufferedInputStream clientIn = new BufferedInputStream(pSocket.getInputStream());
270            BufferedOutputStream clientOut = new BufferedOutputStream(pSocket.getOutputStream());
271
272            // the socket to the remote server
273            Socket server = null;
274
275            // other variables
276            byte[] request = null;
277            byte[] response = null;
278            int requestLength = 0;
279            int responseLength = 0;
280            int pos = -1;
281            StringBuffer host = new StringBuffer("");
282            String hostName = "";
283            int hostPort = 80;
284
285            // get the header info (the web browser won't disconnect after
286            // it's sent a request, so make sure the waitForDisconnect
287            // parameter is false)
288            request = getHTTPData(clientIn, host, false);
289            requestLength = Array.getLength(request);
290
291            // separate the host name from the host port, if necessary
292            // (like if it's "servername:8000")
293            hostName = host.toString();
294            pos = hostName.indexOf(":");
295            if (pos > 0) {
296                try {
297                    hostPort = Integer.parseInt(hostName.substring(pos + 1));
298                } catch (Exception e) {
299                }
300                hostName = hostName.substring(0, pos);
301            }
302
303            // either forward this request to another proxy server or
304            // send it straight to the Host
305            try {
306                if ((fwdServer.length() > 0) && (fwdPort > 0)) {
307                    server = new Socket(fwdServer, fwdPort);
308                } else {
309                    server = new Socket(hostName, hostPort);
310                }
311            } catch (Exception e) {
312                // tell the client there was an error
313                String errMsg = "HTTP/1.0 500\nContent Type: text/plain\n\n"
314                        + "Error connecting to the server:\n" + e + "\n";
315                clientOut.write(errMsg.getBytes(), 0, errMsg.length());
316            }
317
318            if (server != null) {
319                server.setSoTimeout(socketTimeout);
320                BufferedInputStream serverIn = new BufferedInputStream(server.getInputStream());
321                BufferedOutputStream serverOut = new BufferedOutputStream(server.getOutputStream());
322
323                // send the request out
324                serverOut.write(request, 0, requestLength);
325                serverOut.flush();
326
327                // and get the response; if we're not at a debug level that
328                // requires us to return the data in the response, just stream
329                // it back to the client to save ourselves from having to
330                // create and destroy an unnecessary byte array. Also, we
331                // should set the waitForDisconnect parameter to 'true',
332                // because some servers (like Google) don't always set the
333                // Content-Length header field, so we have to listen until
334                // they decide to disconnect (or the connection times out).
335                if (debugLevel > 1) {
336                    response = getHTTPData(serverIn, true);
337                    responseLength = Array.getLength(response);
338                } else {
339                    responseLength = streamHTTPData(serverIn, clientOut, true);
340                }
341
342                serverIn.close();
343                serverOut.close();
344            }
345
346            // send the response back to the client, if we haven't already
347            if (debugLevel > 1) {
348                clientOut.write(response, 0, responseLength);
349            }
350
351            // if the user wants debug info, send them debug info; however,
352            // keep in mind that because we're using threads, the output won't
353            // necessarily be synchronous
354            if (debugLevel > 0) {
355                long endTime = System.currentTimeMillis();
356                debugOut.println("Request from " + pSocket.getInetAddress().getHostAddress()
357                        + " on Port " + pSocket.getLocalPort()
358                        + " to host " + hostName + ":" + hostPort
359                        + "\n  (" + requestLength + " bytes sent, "
360                        + responseLength + " bytes returned, "
361                        + Long.toString(endTime - startTime) + " ms elapsed)");
362                debugOut.flush();
363            }
364            if (debugLevel > 1) {
365                debugOut.println("REQUEST:\n" + (new String(request)));
366                debugOut.println("RESPONSE:\n" + (new String(response)));
367                debugOut.flush();
368            }
369
370            // close all the client streams so we can listen again
371            clientOut.close();
372            clientIn.close();
373            pSocket.close();
374        } catch (Exception e) {
375            if (debugLevel > 0) {
376                debugOut.println("Error in ProxyThread: " + e);
377            }
378            //e.printStackTrace();
379        }
380
381    }
382
383    private byte[] getHTTPData(InputStream in, boolean waitForDisconnect) {
384        // get the HTTP data from an InputStream, and return it as
385        // a byte array
386        // the waitForDisconnect parameter tells us what to do in case
387        // the HTTP header doesn't specify the Content-Length of the
388        // transmission
389        StringBuffer foo = new StringBuffer("");
390        return getHTTPData(in, foo, waitForDisconnect);
391    }
392
393    private byte[] getHTTPData(InputStream in, StringBuffer host, boolean waitForDisconnect) {
394        // get the HTTP data from an InputStream, and return it as
395        // a byte array, and also return the Host entry in the header,
396        // if it's specified -- note that we have to use a StringBuffer
397        // for the 'host' variable, because a String won't return any
398        // information when it's used as a parameter like that
399        ByteArrayOutputStream bs = new ByteArrayOutputStream();
400        streamHTTPData(in, bs, host, waitForDisconnect);
401        return bs.toByteArray();
402    }
403
404    private int streamHTTPData(InputStream in, OutputStream out, boolean waitForDisconnect) {
405        StringBuffer foo = new StringBuffer("");
406        return streamHTTPData(in, out, foo, waitForDisconnect);
407    }
408
409    private int streamHTTPData(InputStream in, OutputStream out, StringBuffer host, boolean waitForDisconnect) {
410        // get the HTTP data from an InputStream, and send it to
411        // the designated OutputStream
412        StringBuffer header = new StringBuffer("");
413        String data = "";
414        int responseCode = 200;
415        int contentLength = 0;
416        int pos = -1;
417        int byteCount = 0;
418
419        try {
420            // get the first line of the header, so we know the response code
421            data = readLine(in);
422            if (data != null) {
423                header.append(data + "\r\n");
424                pos = data.indexOf(" ");
425                if ((data.toLowerCase().startsWith("http")) && (pos >= 0) && (data.indexOf(" ", pos + 1) >= 0)) {
426                    String rcString = data.substring(pos + 1, data.indexOf(" ", pos + 1));
427                    try {
428                        responseCode = Integer.parseInt(rcString);
429                    } catch (Exception e) {
430                        if (debugLevel > 0) {
431                            debugOut.println("Error parsing response code " + rcString);
432                        }
433                    }
434                }
435            }
436
437            // get the rest of the header info
438            while ((data = readLine(in)) != null) {
439                // the header ends at the first blank line
440                if (data.length() == 0) {
441                    break;
442                }
443                //header.append(data + "\r\n"); //FMG commentlendi, cookie değiştireceğimiz için aşağıya taşıyorum
444
445                // check for the Host header
446                pos = data.toLowerCase().indexOf("host:");
447                if (pos >= 0) {
448                    host.setLength(0);
449                    host.append(data.substring(pos + 5).trim());
450                }
451
452                // check for the Content-Length header
453                pos = data.toLowerCase().indexOf("content-length:");
454                if (pos >= 0) {
455                    contentLength = Integer.parseInt(data.substring(pos + 15).trim());
456                }
457
458                //**************************************************************
459                //FMG>>: cookie al, Cookie: ya da Set-Cookie: header'larını al.
460                //Cookie içindeki Safari->key değerini olması gerekenle değiştir (safariKey)
461                //Set-Cookie ile gelen Safari->key değerini güncelle (safariKey)
462
463                //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
464                //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
465
466                // check for the Set-Cookie header
467                pos = data.toLowerCase().indexOf("set-cookie:");
468                if (pos >= 0) {
469                    String setCookie = data.substring(pos + 11).trim();
470                    int safariKeyIndexStart = setCookie.indexOf("&key=") + 5;
471                    int safariKeyIndexEnd = setCookie.indexOf("&", safariKeyIndexStart);
472
473                    if (safariKeyIndexStart >= 5) { //YAP: add extra controls, key cookie may be present in other sites as well. may be limit this to safari host
474                        //get the key from response header
475                        String key = setCookie.substring(safariKeyIndexStart, safariKeyIndexEnd);
476                        SimpleProxy.safariKey = key;
477                        System.out.println("Safari Key in the set-cookie is: " + SimpleProxy.safariKey);
478                    }
479                }
480
481                // check for the Cookie header
482                pos = data.toLowerCase().indexOf("cookie:");
483                if (pos >= 0) {
484                    boolean logged = false;
485
486                    int loggedStart = data.indexOf("&logged=") + 8;
487                    int loggedEnd = data.indexOf("&", loggedStart);
488                    if (loggedStart >= 8) {
489                        logged = data.substring(loggedStart, loggedEnd).equals("true");
490                    }
491
492                    int safariKeyIndexStart = data.indexOf("&key=") + 5;
493                    int safariKeyIndexEnd = data.indexOf("&", safariKeyIndexStart);
494
495                    if (logged && (safariKeyIndexStart >= 5)) { //YAP: add extra controls, key cookie may be present in other sites as well. may be limit this to safari host
496                        //replace the key in the cookie with safariKey
497                        String cookie1 = data.substring(0, safariKeyIndexStart); //first part
498                        String cookie2 = data.substring(safariKeyIndexEnd); //remaining part
499
500                        System.out.println("Old cookie:        '" + data + "'");
501                        data = (cookie1 + SimpleProxy.safariKey + cookie2);
502                        System.out.println("Changed cookie to: '" + data + "'");
503                    }
504                }
505
506                //<<FMG
507                //**************************************************************
508
509                header.append(data + "\r\n");
510                data = "";
511            }
512
513            // add a blank line to terminate the header info
514            header.append("\r\n");
515
516            // convert the header to a byte array, and write it to our stream
517            out.write(header.toString().getBytes(), 0, header.length());
518
519            // if the header indicated that this was not a 200 response,
520            // just return what we've got if there is no Content-Length,
521            // because we may not be getting anything else
522            if ((responseCode != 200) && (contentLength == 0)) {
523                out.flush();
524                return header.length();
525            }
526
527            // get the body, if any; we try to use the Content-Length header to
528            // determine how much data we're supposed to be getting, because
529            // sometimes the client/server won't disconnect after sending us
530            // information...
531            if (contentLength > 0) {
532                waitForDisconnect = false;
533            }
534
535            if ((contentLength > 0) || (waitForDisconnect)) {
536                try {
537                    byte[] buf = new byte[4096];
538                    int bytesIn = 0;
539                    while (((byteCount < contentLength) || (waitForDisconnect))
540                            && ((bytesIn = in.read(buf)) >= 0)) {
541                        out.write(buf, 0, bytesIn);
542                        byteCount += bytesIn;
543                    }
544                } catch (Exception e) {
545                    String errMsg = "Error getting HTTP body: " + e;
546                    if (debugLevel > 0) {
547                        debugOut.println(errMsg);
548                    }
549                    //bs.write(errMsg.getBytes(), 0, errMsg.length());
550                }
551            }
552        } catch (Exception e) {
553            if (debugLevel > 0) {
554                debugOut.println("Error getting HTTP data: " + e);
555            }
556            e.printStackTrace();
557        }
558
559        //flush the OutputStream and return
560        try {
561            out.flush();
562        } catch (Exception e) {
563        }
564        return (header.length() + byteCount);
565    }
566
567    private String readLine(InputStream in) {
568        // reads a line of text from an InputStream
569        StringBuffer data = new StringBuffer("");
570        int c;
571
572        try {
573            // if we have nothing to read, just return null
574            in.mark(1);
575            if (in.read() == -1) {
576                return null;
577            } else {
578                in.reset();
579            }
580
581            while ((c = in.read()) >= 0) {
582                // check for an end-of-line character
583                if ((c == 0) || (c == 10) || (c == 13)) {
584                    break;
585                } else {
586                    data.append((char) c);
587                }
588            }
589
590            // deal with the case where the end-of-line terminator is \r\n
591            if (c == 13) {
592                in.mark(1);
593                if (in.read() != 10) {
594                    in.reset();
595                }
596            }
597        } catch (Exception e) {
598            if (debugLevel > 0) {
599                debugOut.println("Error getting header: " + e);
600            }
601            e.printStackTrace();
602        }
603
604        // and return what we have
605        return data.toString();
606    }
607}
Note: See TracBrowser for help on using the repository browser.