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 */ main(String[] args)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 */ Webserver(String port)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 */ Webserver(int port)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 */ setClassPool(ClassPool loader)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 */ addTranslator(ClassPool cp, Translator t)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 */ end()134 public void end() throws IOException { 135 socket.close(); 136 } 137 138 /** 139 * Prints a log message. 140 */ logging(String msg)141 public void logging(String msg) { 142 System.out.println(msg); 143 } 144 145 /** 146 * Prints a log message. 147 */ logging(String msg1, String msg2)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 */ logging(String msg1, String msg2, String msg3)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 */ logging2(String msg)168 public void logging2(String msg) { 169 System.out.print(" "); 170 System.out.println(msg); 171 } 172 173 /** 174 * Begins the HTTP service. 175 */ run()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 process(Socket clnt)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 readLine(InputStream in)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 skipLine(InputStream in)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 */ doReply(InputStream in, OutputStream out, String cmd)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 checkFilename(String filename, int len)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 letUsersSendClassfile(OutputStream out, String filename, int length)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 sendHeader(OutputStream out, long dataLength, int filetype)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 replyError(OutputStream out, BadHttpRequest e)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 ServiceThread(Webserver w, Socket s)395 public ServiceThread(Webserver w, Socket s) { 396 web = w; 397 sock = s; 398 } 399 run()400 public void run() { 401 try { 402 web.process(sock); 403 } 404 catch (IOException e) { 405 } 406 } 407 } 408