001: /*
002: * Javassist, a Java-bytecode translator toolkit.
003: * Copyright (C) 1999-2006 Shigeru Chiba. All Rights Reserved.
004: *
005: * The contents of this file are subject to the Mozilla Public License Version
006: * 1.1 (the "License"); you may not use this file except in compliance with
007: * the License. Alternatively, the contents of this file may be used under
008: * the terms of the GNU Lesser General Public License Version 2.1 or later.
009: *
010: * Software distributed under the License is distributed on an "AS IS" basis,
011: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
012: * for the specific language governing rights and limitations under the
013: * License.
014: */
015:
016: package javassist.tools.web;
017:
018: import java.net.*;
019: import java.io.*;
020: import java.util.Date;
021: import javassist.*;
022:
023: /**
024: * A web server for running sample programs.
025: *
026: * <p>This enables a Java program to instrument class files loaded by
027: * web browsers for applets. Since the (standard) security manager
028: * does not allow an applet to create and use a class loader,
029: * instrumenting class files must be done by this web server.
030: *
031: * <p><b>Note:</b> although this class is included in the Javassist API,
032: * it is provided as a sample implementation of the web server using
033: * Javassist. Especially, there might be security flaws in this server.
034: * Please use this with YOUR OWN RISK.
035: */
036: public class Webserver {
037: private ServerSocket socket;
038: private ClassPool classPool;
039: protected Translator translator;
040:
041: private final static byte[] endofline = { 0x0d, 0x0a };
042: private byte[] filebuffer = new byte[4096];
043:
044: private final static int typeHtml = 1;
045: private final static int typeClass = 2;
046: private final static int typeGif = 3;
047: private final static int typeJpeg = 4;
048: private final static int typeText = 5;
049:
050: /**
051: * If this field is not null, the class files taken from
052: * <code>ClassPool</code> are written out under the directory
053: * specified by this field. The directory name must not end
054: * with a directory separator.
055: */
056: public String debugDir = null;
057:
058: /**
059: * The top directory of html (and .gif, .class, ...) files.
060: * It must end with the directory separator such as "/".
061: * (For portability, "/" should be used as the directory separator.
062: * Javassist automatically translates "/" into a platform-dependent
063: * character.)
064: * If this field is null, the top directory is the current one where
065: * the JVM is running.
066: *
067: * <p>If the given URL indicates a class file and the class file
068: * is not found under the directory specified by this variable,
069: * then <code>Class.getResourceAsStream()</code> is called
070: * for searching the Java class paths.
071: */
072: public String htmlfileBase = null;
073:
074: /**
075: * Starts a web server.
076: * The port number is specified by the first argument.
077: */
078: public static void main(String[] args) throws IOException {
079: if (args.length == 1) {
080: Webserver web = new Webserver(args[0]);
081: web.run();
082: } else
083: System.err
084: .println("Usage: java javassist.tools.web.Webserver <port number>");
085: }
086:
087: /**
088: * Constructs a web server.
089: *
090: * @param port port number
091: */
092: public Webserver(String port) throws IOException {
093: this (Integer.parseInt(port));
094: }
095:
096: /**
097: * Constructs a web server.
098: *
099: * @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: classPool = cp;
126: translator = t;
127: t.start(classPool);
128: }
129:
130: /**
131: * Closes the socket.
132: */
133: public void end() throws IOException {
134: socket.close();
135: }
136:
137: /**
138: * Prints a log message.
139: */
140: public void logging(String msg) {
141: System.out.println(msg);
142: }
143:
144: /**
145: * Prints a log message.
146: */
147: public void logging(String msg1, String msg2) {
148: System.out.print(msg1);
149: System.out.print(" ");
150: System.out.println(msg2);
151: }
152:
153: /**
154: * Prints a log message.
155: */
156: public void logging(String msg1, String msg2, String msg3) {
157: System.out.print(msg1);
158: System.out.print(" ");
159: System.out.print(msg2);
160: System.out.print(" ");
161: System.out.println(msg3);
162: }
163:
164: /**
165: * Prints a log message with indentation.
166: */
167: public void logging2(String msg) {
168: System.out.print(" ");
169: System.out.println(msg);
170: }
171:
172: /**
173: * Begins the HTTP service.
174: */
175: public void run() {
176: System.err.println("ready to service...");
177: for (;;)
178: try {
179: ServiceThread th = new ServiceThread(this , socket
180: .accept());
181: th.start();
182: } catch (IOException e) {
183: logging(e.toString());
184: }
185: }
186:
187: final void process(Socket clnt) throws IOException {
188: InputStream in = new BufferedInputStream(clnt.getInputStream());
189: String cmd = readLine(in);
190: logging(clnt.getInetAddress().getHostName(), new Date()
191: .toString(), cmd);
192: while (skipLine(in) > 0) {
193: }
194:
195: OutputStream out = new BufferedOutputStream(clnt
196: .getOutputStream());
197: try {
198: doReply(in, out, cmd);
199: } catch (BadHttpRequest e) {
200: replyError(out, e);
201: }
202:
203: out.flush();
204: in.close();
205: out.close();
206: clnt.close();
207: }
208:
209: private String readLine(InputStream in) throws IOException {
210: StringBuffer buf = new StringBuffer();
211: int c;
212: while ((c = in.read()) >= 0 && c != 0x0d)
213: buf.append((char) c);
214:
215: in.read(); /* skip 0x0a (LF) */
216: return buf.toString();
217: }
218:
219: private int skipLine(InputStream in) throws IOException {
220: int c;
221: int len = 0;
222: while ((c = in.read()) >= 0 && c != 0x0d)
223: ++len;
224:
225: in.read(); /* skip 0x0a (LF) */
226: return len;
227: }
228:
229: /**
230: * Proceses a HTTP request from a client.
231: *
232: * @param out the output stream to a client
233: * @param cmd the command received from a client
234: */
235: public void doReply(InputStream in, OutputStream out, String cmd)
236: throws IOException, BadHttpRequest {
237: int len;
238: int fileType;
239: String filename, urlName;
240:
241: if (cmd.startsWith("GET /"))
242: filename = urlName = cmd.substring(5, cmd.indexOf(' ', 5));
243: else
244: throw new BadHttpRequest();
245:
246: if (filename.endsWith(".class"))
247: fileType = typeClass;
248: else if (filename.endsWith(".html")
249: || filename.endsWith(".htm"))
250: fileType = typeHtml;
251: else if (filename.endsWith(".gif"))
252: fileType = typeGif;
253: else if (filename.endsWith(".jpg"))
254: fileType = typeJpeg;
255: else
256: fileType = typeText; // or textUnknown
257:
258: len = filename.length();
259: if (fileType == typeClass
260: && letUsersSendClassfile(out, filename, len))
261: return;
262:
263: checkFilename(filename, len);
264: if (htmlfileBase != null)
265: filename = htmlfileBase + filename;
266:
267: if (File.separatorChar != '/')
268: filename = filename.replace('/', File.separatorChar);
269:
270: File file = new File(filename);
271: if (file.canRead()) {
272: sendHeader(out, file.length(), fileType);
273: FileInputStream fin = new FileInputStream(file);
274: for (;;) {
275: len = fin.read(filebuffer);
276: if (len <= 0)
277: break;
278: else
279: out.write(filebuffer, 0, len);
280: }
281:
282: fin.close();
283: return;
284: }
285:
286: // If the file is not found under the html-file directory,
287: // then Class.getResourceAsStream() is tried.
288:
289: if (fileType == typeClass) {
290: InputStream fin = getClass().getResourceAsStream(
291: "/" + urlName);
292: if (fin != null) {
293: ByteArrayOutputStream barray = new ByteArrayOutputStream();
294: for (;;) {
295: len = fin.read(filebuffer);
296: if (len <= 0)
297: break;
298: else
299: barray.write(filebuffer, 0, len);
300: }
301:
302: byte[] classfile = barray.toByteArray();
303: sendHeader(out, classfile.length, typeClass);
304: out.write(classfile);
305: fin.close();
306: return;
307: }
308: }
309:
310: throw new BadHttpRequest();
311: }
312:
313: private void checkFilename(String filename, int len)
314: throws BadHttpRequest {
315: for (int i = 0; i < len; ++i) {
316: char c = filename.charAt(i);
317: if (!Character.isJavaIdentifierPart(c) && c != '.'
318: && c != '/')
319: throw new BadHttpRequest();
320: }
321:
322: if (filename.indexOf("..") >= 0)
323: throw new BadHttpRequest();
324: }
325:
326: private boolean letUsersSendClassfile(OutputStream out,
327: String filename, int length) throws IOException,
328: BadHttpRequest {
329: if (classPool == null)
330: return false;
331:
332: byte[] classfile;
333: String classname = filename.substring(0, length - 6).replace(
334: '/', '.');
335: try {
336: if (translator != null)
337: translator.onLoad(classPool, classname);
338:
339: CtClass c = classPool.get(classname);
340: classfile = c.toBytecode();
341: if (debugDir != null)
342: c.writeFile(debugDir);
343: } catch (Exception e) {
344: throw new BadHttpRequest(e);
345: }
346:
347: sendHeader(out, classfile.length, typeClass);
348: out.write(classfile);
349: return true;
350: }
351:
352: private void sendHeader(OutputStream out, long dataLength,
353: int filetype) throws IOException {
354: out.write("HTTP/1.0 200 OK".getBytes());
355: out.write(endofline);
356: out.write("Content-Length: ".getBytes());
357: out.write(Long.toString(dataLength).getBytes());
358: out.write(endofline);
359: if (filetype == typeClass)
360: out.write("Content-Type: application/octet-stream"
361: .getBytes());
362: else if (filetype == typeHtml)
363: out.write("Content-Type: text/html".getBytes());
364: else if (filetype == typeGif)
365: out.write("Content-Type: image/gif".getBytes());
366: else if (filetype == typeJpeg)
367: out.write("Content-Type: image/jpg".getBytes());
368: else if (filetype == typeText)
369: out.write("Content-Type: text/plain".getBytes());
370:
371: out.write(endofline);
372: out.write(endofline);
373: }
374:
375: private void replyError(OutputStream out, BadHttpRequest e)
376: throws IOException {
377: logging2("bad request: " + e.toString());
378: out.write("HTTP/1.0 400 Bad Request".getBytes());
379: out.write(endofline);
380: out.write(endofline);
381: out.write("<H1>Bad Request</H1>".getBytes());
382: }
383: }
384:
385: class ServiceThread extends Thread {
386: Webserver web;
387: Socket sock;
388:
389: public ServiceThread(Webserver w, Socket s) {
390: web = w;
391: sock = s;
392: }
393:
394: public void run() {
395: try {
396: web.process(sock);
397: } catch (IOException e) {
398: }
399: }
400: }
|