/Users/lyon/j4p/src/javassist/web/Webserver.java

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