/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