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