Home | History | Annotate | Download | only in web
      1 /*
      2  * Javassist, a Java-bytecode translator toolkit.
      3  * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
      4  *
      5  * The contents of this file are subject to the Mozilla Public License Version
      6  * 1.1 (the "License"); you may not use this file except in compliance with
      7  * the License.  Alternatively, the contents of this file may be used under
      8  * the terms of the GNU Lesser General Public License Version 2.1 or later.
      9  *
     10  * Software distributed under the License is distributed on an "AS IS" basis,
     11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
     12  * for the specific language governing rights and limitations under the
     13  * License.
     14  */
     15 
     16 package javassist.tools.web;
     17 
     18 import java.net.*;
     19 import java.io.*;
     20 import java.util.Date;
     21 import javassist.*;
     22 
     23 /**
     24  * A web server for running sample programs.
     25  *
     26  * <p>This enables a Java program to instrument class files loaded by
     27  * web browsers for applets.  Since the (standard) security manager
     28  * does not allow an applet to create and use a class loader,
     29  * instrumenting class files must be done by this web server.
     30  *
     31  * <p><b>Note:</b> although this class is included in the Javassist API,
     32  * it is provided as a sample implementation of the web server using
     33  * Javassist.  Especially, there might be security flaws in this server.
     34  * Please use this with YOUR OWN RISK.
     35  */
     36 public class Webserver {
     37     private ServerSocket socket;
     38     private ClassPool classPool;
     39     protected Translator translator;
     40 
     41     private final static byte[] endofline = { 0x0d, 0x0a };
     42 
     43     private final static int typeHtml = 1;
     44     private final static int typeClass = 2;
     45     private final static int typeGif = 3;
     46     private final static int typeJpeg = 4;
     47     private final static int typeText = 5;
     48 
     49     /**
     50      * If this field is not null, the class files taken from
     51      * <code>ClassPool</code> are written out under the directory
     52      * specified by this field.  The directory name must not end
     53      * with a directory separator.
     54      */
     55     public String debugDir = null;
     56 
     57     /**
     58      * The top directory of html (and .gif, .class, ...) files.
     59      * It must end with the directory separator such as "/".
     60      * (For portability, "/" should be used as the directory separator.
     61      * Javassist automatically translates "/" into a platform-dependent
     62      * character.)
     63      * If this field is null, the top directory is the current one where
     64      * the JVM is running.
     65      *
     66      * <p>If the given URL indicates a class file and the class file
     67      * is not found under the directory specified by this variable,
     68      * then <code>Class.getResourceAsStream()</code> is called
     69      * for searching the Java class paths.
     70      */
     71     public String htmlfileBase = null;
     72 
     73     /**
     74      * Starts a web server.
     75      * The port number is specified by the first argument.
     76      */
     77     public static void main(String[] args) throws IOException {
     78         if (args.length == 1) {
     79             Webserver web = new Webserver(args[0]);
     80             web.run();
     81         }
     82         else
     83             System.err.println(
     84                         "Usage: java javassist.tools.web.Webserver <port number>");
     85     }
     86 
     87     /**
     88      * Constructs a web server.
     89      *
     90      * @param port      port number
     91      */
     92     public Webserver(String port) throws IOException {
     93         this(Integer.parseInt(port));
     94     }
     95 
     96     /**
     97      * Constructs a web server.
     98      *
     99      * @param port      port number
    100      */
    101     public Webserver(int port) throws IOException {
    102         socket = new ServerSocket(port);
    103         classPool = null;
    104         translator = null;
    105     }
    106 
    107     /**
    108      * Requests the web server to use the specified
    109      * <code>ClassPool</code> object for obtaining a class file.
    110      */
    111     public void setClassPool(ClassPool loader) {
    112         classPool = loader;
    113     }
    114 
    115     /**
    116      * Adds a translator, which is called whenever a client requests
    117      * a class file.
    118      *
    119      * @param cp        the <code>ClassPool</code> object for obtaining
    120      *                  a class file.
    121      * @param t         a translator.
    122      */
    123     public void addTranslator(ClassPool cp, Translator t)
    124         throws NotFoundException, CannotCompileException
    125     {
    126         classPool = cp;
    127         translator = t;
    128         t.start(classPool);
    129     }
    130 
    131     /**
    132      * Closes the socket.
    133      */
    134     public void end() throws IOException {
    135         socket.close();
    136     }
    137 
    138     /**
    139      * Prints a log message.
    140      */
    141     public void logging(String msg) {
    142         System.out.println(msg);
    143     }
    144 
    145     /**
    146      * Prints a log message.
    147      */
    148     public void logging(String msg1, String msg2) {
    149         System.out.print(msg1);
    150         System.out.print(" ");
    151         System.out.println(msg2);
    152     }
    153 
    154     /**
    155      * Prints a log message.
    156      */
    157     public void logging(String msg1, String msg2, String msg3) {
    158         System.out.print(msg1);
    159         System.out.print(" ");
    160         System.out.print(msg2);
    161         System.out.print(" ");
    162         System.out.println(msg3);
    163     }
    164 
    165     /**
    166      * Prints a log message with indentation.
    167      */
    168     public void logging2(String msg) {
    169         System.out.print("    ");
    170         System.out.println(msg);
    171     }
    172 
    173     /**
    174      * Begins the HTTP service.
    175      */
    176     public void run() {
    177         System.err.println("ready to service...");
    178         for (;;)
    179             try {
    180                 ServiceThread th = new ServiceThread(this, socket.accept());
    181                 th.start();
    182             }
    183             catch (IOException e) {
    184                 logging(e.toString());
    185             }
    186     }
    187 
    188     final void process(Socket clnt) throws IOException {
    189         InputStream in = new BufferedInputStream(clnt.getInputStream());
    190         String cmd = readLine(in);
    191         logging(clnt.getInetAddress().getHostName(),
    192                 new Date().toString(), cmd);
    193         while (skipLine(in) > 0){
    194         }
    195 
    196         OutputStream out = new BufferedOutputStream(clnt.getOutputStream());
    197         try {
    198             doReply(in, out, cmd);
    199         }
    200         catch (BadHttpRequest e) {
    201             replyError(out, e);
    202         }
    203 
    204         out.flush();
    205         in.close();
    206         out.close();
    207         clnt.close();
    208     }
    209 
    210     private String readLine(InputStream in) throws IOException {
    211         StringBuffer buf = new StringBuffer();
    212         int c;
    213         while ((c = in.read()) >= 0 && c != 0x0d)
    214             buf.append((char)c);
    215 
    216         in.read();      /* skip 0x0a (LF) */
    217         return buf.toString();
    218     }
    219 
    220     private int skipLine(InputStream in) throws IOException {
    221         int c;
    222         int len = 0;
    223         while ((c = in.read()) >= 0 && c != 0x0d)
    224             ++len;
    225 
    226         in.read();      /* skip 0x0a (LF) */
    227         return len;
    228     }
    229 
    230     /**
    231      * Proceses a HTTP request from a client.
    232      *
    233      * @param out       the output stream to a client
    234      * @param cmd       the command received from a client
    235      */
    236     public void doReply(InputStream in, OutputStream out, String cmd)
    237         throws IOException, BadHttpRequest
    238     {
    239         int len;
    240         int fileType;
    241         String filename, urlName;
    242 
    243         if (cmd.startsWith("GET /"))
    244             filename = urlName = cmd.substring(5, cmd.indexOf(' ', 5));
    245         else
    246             throw new BadHttpRequest();
    247 
    248         if (filename.endsWith(".class"))
    249             fileType = typeClass;
    250         else if (filename.endsWith(".html") || filename.endsWith(".htm"))
    251             fileType = typeHtml;
    252         else if (filename.endsWith(".gif"))
    253             fileType = typeGif;
    254         else if (filename.endsWith(".jpg"))
    255             fileType = typeJpeg;
    256         else
    257             fileType = typeText;        // or textUnknown
    258 
    259         len = filename.length();
    260         if (fileType == typeClass
    261             && letUsersSendClassfile(out, filename, len))
    262             return;
    263 
    264         checkFilename(filename, len);
    265         if (htmlfileBase != null)
    266             filename = htmlfileBase + filename;
    267 
    268         if (File.separatorChar != '/')
    269             filename = filename.replace('/', File.separatorChar);
    270 
    271         File file = new File(filename);
    272         if (file.canRead()) {
    273             sendHeader(out, file.length(), fileType);
    274             FileInputStream fin = new FileInputStream(file);
    275             byte[] filebuffer = new byte[4096];
    276             for (;;) {
    277                 len = fin.read(filebuffer);
    278                 if (len <= 0)
    279                     break;
    280                 else
    281                     out.write(filebuffer, 0, len);
    282             }
    283 
    284             fin.close();
    285             return;
    286         }
    287 
    288         // If the file is not found under the html-file directory,
    289         // then Class.getResourceAsStream() is tried.
    290 
    291         if (fileType == typeClass) {
    292             InputStream fin
    293                 = getClass().getResourceAsStream("/" + urlName);
    294             if (fin != null) {
    295                 ByteArrayOutputStream barray = new ByteArrayOutputStream();
    296                 byte[] filebuffer = new byte[4096];
    297                 for (;;) {
    298                     len = fin.read(filebuffer);
    299                     if (len <= 0)
    300                         break;
    301                     else
    302                         barray.write(filebuffer, 0, len);
    303                 }
    304 
    305                 byte[] classfile = barray.toByteArray();
    306                 sendHeader(out, classfile.length, typeClass);
    307                 out.write(classfile);
    308                 fin.close();
    309                 return;
    310             }
    311         }
    312 
    313         throw new BadHttpRequest();
    314     }
    315 
    316     private void checkFilename(String filename, int len)
    317         throws BadHttpRequest
    318     {
    319         for (int i = 0; i < len; ++i) {
    320             char c = filename.charAt(i);
    321             if (!Character.isJavaIdentifierPart(c) && c != '.' && c != '/')
    322                 throw new BadHttpRequest();
    323         }
    324 
    325         if (filename.indexOf("..") >= 0)
    326             throw new BadHttpRequest();
    327     }
    328 
    329     private boolean letUsersSendClassfile(OutputStream out,
    330                                           String filename, int length)
    331         throws IOException, BadHttpRequest
    332     {
    333         if (classPool == null)
    334             return false;
    335 
    336         byte[] classfile;
    337         String classname
    338             = filename.substring(0, length - 6).replace('/', '.');
    339         try {
    340             if (translator != null)
    341                 translator.onLoad(classPool, classname);
    342 
    343             CtClass c = classPool.get(classname);
    344             classfile = c.toBytecode();
    345             if (debugDir != null)
    346                 c.writeFile(debugDir);
    347         }
    348         catch (Exception e) {
    349             throw new BadHttpRequest(e);
    350         }
    351 
    352         sendHeader(out, classfile.length, typeClass);
    353         out.write(classfile);
    354         return true;
    355     }
    356 
    357     private void sendHeader(OutputStream out, long dataLength, int filetype)
    358         throws IOException
    359     {
    360         out.write("HTTP/1.0 200 OK".getBytes());
    361         out.write(endofline);
    362         out.write("Content-Length: ".getBytes());
    363         out.write(Long.toString(dataLength).getBytes());
    364         out.write(endofline);
    365         if (filetype == typeClass)
    366             out.write("Content-Type: application/octet-stream".getBytes());
    367         else if (filetype == typeHtml)
    368             out.write("Content-Type: text/html".getBytes());
    369         else if (filetype == typeGif)
    370             out.write("Content-Type: image/gif".getBytes());
    371         else if (filetype == typeJpeg)
    372             out.write("Content-Type: image/jpg".getBytes());
    373         else if (filetype == typeText)
    374             out.write("Content-Type: text/plain".getBytes());
    375 
    376         out.write(endofline);
    377         out.write(endofline);
    378     }
    379 
    380     private void replyError(OutputStream out, BadHttpRequest e)
    381         throws IOException
    382     {
    383         logging2("bad request: " + e.toString());
    384         out.write("HTTP/1.0 400 Bad Request".getBytes());
    385         out.write(endofline);
    386         out.write(endofline);
    387         out.write("<H1>Bad Request</H1>".getBytes());
    388     }
    389 }
    390 
    391 class ServiceThread extends Thread {
    392     Webserver web;
    393     Socket sock;
    394 
    395     public ServiceThread(Webserver w, Socket s) {
    396         web = w;
    397         sock = s;
    398     }
    399 
    400     public void run() {
    401         try {
    402             web.process(sock);
    403         }
    404         catch (IOException e) {
    405         }
    406     }
    407 }
    408