001: /* ====================================================================
002: * The Jcorporate Apache Style Software License, Version 1.2 05-07-2002
003: *
004: * Copyright (c) 1995-2002 Jcorporate Ltd. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * 2. Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * 3. The end-user documentation included with the redistribution,
019: * if any, must include the following acknowledgment:
020: * "This product includes software developed by Jcorporate Ltd.
021: * (http://www.jcorporate.com/)."
022: * Alternately, this acknowledgment may appear in the software itself,
023: * if and wherever such third-party acknowledgments normally appear.
024: *
025: * 4. "Jcorporate" and product names such as "Expresso" must
026: * not be used to endorse or promote products derived from this
027: * software without prior written permission. For written permission,
028: * please contact info@jcorporate.com.
029: *
030: * 5. Products derived from this software may not be called "Expresso",
031: * or other Jcorporate product names; nor may "Expresso" or other
032: * Jcorporate product names appear in their name, without prior
033: * written permission of Jcorporate Ltd.
034: *
035: * 6. No product derived from this software may compete in the same
036: * market space, i.e. framework, without prior written permission
037: * of Jcorporate Ltd. For written permission, please contact
038: * partners@jcorporate.com.
039: *
040: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
041: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
042: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
043: * DISCLAIMED. IN NO EVENT SHALL JCORPORATE LTD OR ITS CONTRIBUTORS
044: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
045: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
046: * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
047: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
048: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
049: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
050: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
051: * SUCH DAMAGE.
052: * ====================================================================
053: *
054: * This software consists of voluntary contributions made by many
055: * individuals on behalf of the Jcorporate Ltd. Contributions back
056: * to the project(s) are encouraged when you make modifications.
057: * Please send them to support@jcorporate.com. For more information
058: * on Jcorporate Ltd. and its products, please see
059: * <http://www.jcorporate.com/>.
060: *
061: * Portions of this software are based upon other open source
062: * products and are subject to their respective licenses.
063: */
064:
065: package com.jcorporate.expresso.ext.controller;
066:
067: import com.jcorporate.expresso.core.controller.ControllerException;
068: import com.jcorporate.expresso.core.controller.ControllerRequest;
069: import com.jcorporate.expresso.core.controller.ControllerResponse;
070: import com.jcorporate.expresso.core.controller.DBController;
071: import com.jcorporate.expresso.core.controller.ServletControllerRequest;
072: import com.jcorporate.expresso.core.controller.State;
073: import com.jcorporate.expresso.core.misc.ConfigManager;
074: import com.jcorporate.expresso.core.security.filters.Filter;
075: import org.apache.log4j.Logger;
076:
077: import javax.servlet.Servlet;
078: import javax.servlet.http.HttpServletRequest;
079: import javax.servlet.http.HttpServletResponse;
080: import javax.servlet.http.HttpSession;
081: import java.io.BufferedInputStream;
082: import java.io.ByteArrayOutputStream;
083: import java.io.File;
084: import java.io.FileInputStream;
085: import java.io.FileNotFoundException;
086: import java.io.IOException;
087: import java.io.OutputStream;
088: import java.io.PrintStream;
089: import java.io.PushbackInputStream;
090: import java.util.Date;
091: import java.util.Hashtable;
092: import java.util.Vector;
093:
094: /**
095: * This controller is used to serve up text files. It helps provide a particularly
096: * useful mechanism for tutorial purposes.
097: * <p><b>PLEASE NOTE!</b>: It is DEFINITELY not recommended that this controller
098: * should be opened up to anybody but the Administrator in a production
099: * environment! It could be used by malicious attackers to cause the system
100: * to cough up password files, and many other things that they would not normally
101: * have access to </p>
102: *
103: * @author Michael Rimov - Adapted from code created by Peter Pilgrim.
104: */
105:
106: public class ServeTextFile extends DBController {
107:
108: /**
109: * List of locations to search for this file
110: */
111: static private Vector rootDirList = new Vector();
112:
113: /**
114: * Our Log4J logger. See <a href="http://jakarta.apache.org/log4j/">The Log4j Website</a>
115: * for more information.
116: */
117: transient static private Logger log = Logger
118: .getLogger("expresso.ext.controller.ServeTextFile");
119:
120: /**
121: * A filter that efficiently removes (hopefully) all URL trickery that could
122: * result in unnecessary security problems
123: */
124: transient private static Filter fileNameFilter = null;
125:
126: /**
127: * A filter that efficiently goes through a string and color codes the
128: * appropriate stuff for nice eye candy
129: */
130: transient private static Filter javaCodeFilter = null;
131:
132: //--------------------------------------------------------------
133: //Here we have contstants for color coding java files
134: //--------------------------------------------------------------
135: public final static String DEFAULT_RESERVED_KEYWORD_COLOR = "#9900CC";
136: public final static String DEFAULT_PRIMITIVE_VAR_COLOR = "#008000";
137: public final static String DEFAULT_SPECIAL_KEYWORD_COLOR = "#4682B4"; // Light Blue
138: public final static String DEFAULT_SINGLE_QUOTE_COLOR = "#0066FF";
139: public final static String DEFAULT_DOUBLE_QUOTE_COLOR = "#0000CC";
140: public final static String DEFAULT_CSTYLE_COMMENT_COLOR = "#CC0033";
141: public final static String DEFAULT_CPLUS_COMMENT_COLOR = "B22222"; // firebrick
142: public final static String DEFAULT_DECIMAL_NUMBER_COLOR = "#996600";
143: protected final static String reservedKeywordColor = DEFAULT_RESERVED_KEYWORD_COLOR;
144: protected final static String primitiveVarColor = DEFAULT_PRIMITIVE_VAR_COLOR;
145: protected final static String specialKeywordColor = DEFAULT_SPECIAL_KEYWORD_COLOR;
146: protected final static String singleQuoteColor = DEFAULT_SINGLE_QUOTE_COLOR;
147: protected final static String doubleQuoteColor = DEFAULT_DOUBLE_QUOTE_COLOR;
148: protected final static String cstyleCommentColor = DEFAULT_CSTYLE_COMMENT_COLOR;
149: protected final static String cplusCommentColor = DEFAULT_CPLUS_COMMENT_COLOR;
150: protected final static String decimalNumberColor = DEFAULT_DECIMAL_NUMBER_COLOR;
151:
152: /**
153: * Class used a structure to pass info around calls
154: */
155: class Parameters {
156: HttpServletRequest req; // Request
157: HttpServletResponse res; // Response
158: HttpSession session; // Session
159: String inputFilename; // Input Filename
160: String filename; // Resolved Filename
161: PrintStream out; // Output
162: }
163:
164: /**
165: * A simple structure class to map a Java
166: * reserved keyword to a HTML colour code
167: */
168: static class ReservedWord {
169: String keyword;
170: String htmlcolor;
171:
172: ReservedWord(String keyword, String htmlcolor) {
173: this .keyword = keyword;
174: this .htmlcolor = htmlcolor;
175: }
176: }
177:
178: static protected final ServeTextFile.ReservedWord[] java_reserved_keywords = {
179: new ReservedWord("class", DEFAULT_RESERVED_KEYWORD_COLOR),
180: new ReservedWord("interface",
181: DEFAULT_RESERVED_KEYWORD_COLOR),
182: new ReservedWord("extends", DEFAULT_RESERVED_KEYWORD_COLOR),
183: new ReservedWord("implements",
184: DEFAULT_RESERVED_KEYWORD_COLOR),
185: new ReservedWord("goto", DEFAULT_RESERVED_KEYWORD_COLOR),
186: new ReservedWord("for", DEFAULT_RESERVED_KEYWORD_COLOR),
187: new ReservedWord("return", DEFAULT_RESERVED_KEYWORD_COLOR),
188: new ReservedWord("if", DEFAULT_RESERVED_KEYWORD_COLOR),
189: new ReservedWord("then", DEFAULT_RESERVED_KEYWORD_COLOR),
190: new ReservedWord("else", DEFAULT_RESERVED_KEYWORD_COLOR),
191: new ReservedWord("while", DEFAULT_RESERVED_KEYWORD_COLOR),
192: new ReservedWord("do", DEFAULT_RESERVED_KEYWORD_COLOR),
193: new ReservedWord("switch", DEFAULT_RESERVED_KEYWORD_COLOR),
194: new ReservedWord("case", DEFAULT_RESERVED_KEYWORD_COLOR),
195: new ReservedWord("default", DEFAULT_RESERVED_KEYWORD_COLOR),
196: new ReservedWord("instanceof",
197: DEFAULT_RESERVED_KEYWORD_COLOR),
198: new ReservedWord("package", DEFAULT_RESERVED_KEYWORD_COLOR),
199: new ReservedWord("import", DEFAULT_RESERVED_KEYWORD_COLOR),
200: new ReservedWord("public", DEFAULT_RESERVED_KEYWORD_COLOR),
201: new ReservedWord("protected",
202: DEFAULT_RESERVED_KEYWORD_COLOR),
203: new ReservedWord("private", DEFAULT_RESERVED_KEYWORD_COLOR),
204: new ReservedWord("super", DEFAULT_RESERVED_KEYWORD_COLOR),
205: new ReservedWord("new", DEFAULT_RESERVED_KEYWORD_COLOR),
206: new ReservedWord("this", DEFAULT_RESERVED_KEYWORD_COLOR),
207: new ReservedWord("try", DEFAULT_RESERVED_KEYWORD_COLOR),
208: new ReservedWord("catch", DEFAULT_RESERVED_KEYWORD_COLOR),
209: new ReservedWord("throw", DEFAULT_RESERVED_KEYWORD_COLOR),
210: new ReservedWord("throws", DEFAULT_RESERVED_KEYWORD_COLOR),
211: new ReservedWord("final", DEFAULT_RESERVED_KEYWORD_COLOR),
212: new ReservedWord("abstract", DEFAULT_RESERVED_KEYWORD_COLOR),
213: new ReservedWord("native", DEFAULT_RESERVED_KEYWORD_COLOR),
214: new ReservedWord("static", DEFAULT_RESERVED_KEYWORD_COLOR),
215: new ReservedWord("transient",
216: DEFAULT_RESERVED_KEYWORD_COLOR),
217: new ReservedWord("void", DEFAULT_PRIMITIVE_VAR_COLOR),
218: new ReservedWord("boolean", DEFAULT_PRIMITIVE_VAR_COLOR),
219: new ReservedWord("char", DEFAULT_PRIMITIVE_VAR_COLOR),
220: new ReservedWord("int", DEFAULT_PRIMITIVE_VAR_COLOR),
221: new ReservedWord("short", DEFAULT_PRIMITIVE_VAR_COLOR),
222: new ReservedWord("long", DEFAULT_PRIMITIVE_VAR_COLOR),
223: new ReservedWord("float", DEFAULT_PRIMITIVE_VAR_COLOR),
224: new ReservedWord("double", DEFAULT_PRIMITIVE_VAR_COLOR),
225: new ReservedWord("null", DEFAULT_SPECIAL_KEYWORD_COLOR),
226: new ReservedWord("true", DEFAULT_SPECIAL_KEYWORD_COLOR),
227: new ReservedWord("false", DEFAULT_SPECIAL_KEYWORD_COLOR) };
228:
229: protected static Hashtable fast_keyword_map = null;
230:
231: public ServeTextFile() {
232: State s = new State("serveTextFile", "Serve A Text File");
233: s.addRequiredParameter("filename");
234: this .addState(s);
235:
236: s = new State("serveJavaFile", "Serve a Java File");
237: s.addRequiredParameter("filename");
238: this .addState(s);
239:
240: String baseRootDir = ConfigManager.getWebAppDir()
241: + "/WEB-INF/src";
242: rootDirList.add(baseRootDir);
243: baseRootDir = ConfigManager.getWebAppDir();
244: rootDirList.add(baseRootDir);
245:
246: if (fileNameFilter == null) {
247: //We use this to filter out all URL trickery that we can think of
248: fileNameFilter = new Filter(new String[] { "|", "..", ">",
249: "<" }, new String[] { "", "", "", "" });
250: }
251:
252: //
253: //Initialize the Java keyword map
254: //
255: if (fast_keyword_map == null) {
256: fast_keyword_map = new Hashtable(
257: java_reserved_keywords.length);
258:
259: for (int k = 0; k < java_reserved_keywords.length; ++k) {
260: fast_keyword_map.put(java_reserved_keywords[k].keyword,
261: java_reserved_keywords[k]);
262: }
263: }
264:
265: this .setInitialState("serveJavaFile");
266: this
267: .setSchema(com.jcorporate.expresso.core.ExpressoSchema.class);
268: }
269:
270: /**
271: * Serves up a basic text file as specified by the parameter. Our goal
272: * here is to provide something that is reasonably secure in that we remove
273: * all URL trickery.
274: *
275: * @param request The <code>ControllerRequest</code> Object
276: * @param response The <code>ControllerResponse</code> Object
277: * @return ControllerResponse
278: * @throws ControllerException upon error
279: */
280: protected ControllerResponse runServeTextFileState(
281: ControllerRequest request, ControllerResponse response)
282: throws ControllerException {
283:
284: ServletControllerRequest servRequest;
285: try {
286: servRequest = (ServletControllerRequest) request;
287: } catch (ClassCastException ex) {
288: throw new ControllerException(
289: "This controller must be run within only a http environment");
290: }
291:
292: //
293: //ControllerRequest contains pointers to low level HttpServlet stuff
294: //if it can be downcast to ServletControllerRequest
295: //
296: HttpServletRequest req = (HttpServletRequest) servRequest
297: .getServletRequest();
298: HttpServletResponse res = (HttpServletResponse) servRequest
299: .getServletResponse();
300: Servlet servlet = servRequest.getCallingServlet();
301:
302: String fileSep = System.getProperty("file.separator");
303: String inputFilename = req.getParameter("filename");
304:
305: try {
306:
307: // Append the input filename to the root directory
308: if (inputFilename == null) {
309: throw new ControllerException(
310: "filename parameter is not set.");
311: }
312:
313: //
314: //By running it throuhg the filter, we should be able to remove all
315: //sorts of URL trickery that occurs by people sending urls like a
316: //URL encoded version of:
317: //mail hacker@hotmail.com -s Password! < cat /../../../etc/passwd
318: //(And YES, this kind of attack happens often!)
319: //
320: inputFilename = fileNameFilter.stripFilter(inputFilename);
321:
322: if (inputFilename.length() == 0) {
323: throw new ControllerException(
324: "filename parameter is not set.");
325: }
326:
327: if (!(inputFilename.endsWith(".java")
328: || inputFilename.endsWith(".cpp")
329: || inputFilename.endsWith(".cc")
330: || inputFilename.endsWith(".jpg")
331: || inputFilename.endsWith(".jpeg")
332: || inputFilename.endsWith(".png")
333: || inputFilename.endsWith(".jsp")
334: || inputFilename.endsWith(".txt") || inputFilename
335: .endsWith(".wm"))) {
336: throw new ControllerException(
337: "Sorry, it is forbidden to serve this type of file.");
338: }
339:
340: boolean fileWasServed = false;
341:
342: for (int k = 0; k < rootDirList.size(); ++k) {
343: String rootDir = (String) rootDirList.elementAt(k);
344: File file = new File(rootDir, inputFilename);
345: String filename = file.getPath();
346:
347: if (file.exists()) {
348: if (!file.canRead()) {
349: throw new IOException("not readable filename:`"
350: + inputFilename + "'");
351: }
352: if (file.isDirectory()) {
353: throw new IOException(
354: "cannot serve a directory as filename:`"
355: + inputFilename + "'");
356: }
357:
358: fileWasServed = true; /*!*/
359: //
360: //Set this value to indicate we don't want to do a normal struts forward
361: //
362: response.setCustomResponse(true);
363:
364: // Get the MIME type to return the browser
365: String contentType = servlet.getServletConfig()
366: .getServletContext().getMimeType(filename);
367:
368: if (contentType == null) {
369: contentType = "text/plain";
370: }
371:
372: if (log.isInfoEnabled()) {
373: log
374: .info("BasicFileServeServlet: Serving file:`"
375: + filename
376: + "' type:"
377: + contentType);
378: }
379:
380: // Create a buffer for the file contents
381: ByteArrayOutputStream baos = new ByteArrayOutputStream(
382: 8192);
383:
384: // Extract the file contents
385: returnFile(filename, baos);
386:
387: // Provided no I/O exception occurred, pump the buffered
388: // data to the browser now.
389: OutputStream out = res.getOutputStream();
390: res.setContentType(contentType);
391: baos.writeTo(out);
392: out.flush();
393: out.close();
394: break;
395: }
396: }
397: if (!fileWasServed) {
398: throw new FileNotFoundException("no such file: `"
399: + inputFilename + "'");
400: }
401: } catch (FileNotFoundException ex) {
402: log.error("FileNotFoundException locating file file", ex);
403: throw new ControllerException(
404: "FileNotFoundException Error transferring file", ex);
405: } catch (java.io.IOException ioe) {
406: log.error("I/O Error transferring file", ioe);
407: throw new ControllerException(
408: "I/O Error transferring file", ioe);
409: }
410:
411: return response;
412: }
413:
414: protected void returnFile(String filename, OutputStream out)
415: throws FileNotFoundException, IOException {
416: FileInputStream fis = null;
417:
418: try {
419: fis = new FileInputStream(filename);
420:
421: byte[] buffer = new byte[8192];
422: int bytesRead;
423:
424: while ((bytesRead = fis.read(buffer)) != -1) {
425: out.write(buffer, 0, bytesRead);
426: }
427: } finally {
428: if (fis != null) {
429: fis.close();
430: }
431: }
432: }
433:
434: /**
435: * Serves up a java source file as specified by the parameter. This
436: * state is different from that of servTextFile in that it color codes
437: * all the keywords, etc in the java file.
438: * <p/>
439: * Our goal here is to provide something that is reasonably secure in that we remove
440: * all URL trickery, and we also only serve up NON source code files. Again,
441: * this should be NOT used in a production environment and only exists for
442: * teaching purposes.
443: *
444: * @param request The <code>ControllerRequest</code> Object
445: * @param response The <code>ControllerResponse</code> Object
446: * @return ControllerResponse
447: * @throws ControllerException upon error
448: */
449: protected ControllerResponse runServeJavaFileState(
450: ControllerRequest request, ControllerResponse response)
451: throws ControllerException {
452: ServletControllerRequest servRequest;
453: try {
454: servRequest = (ServletControllerRequest) request;
455: } catch (ClassCastException ex) {
456: throw new ControllerException(
457: "This controller must be run within only a http environment");
458: }
459:
460: //
461: //ControllerRequest contains pointers to low level HttpServlet stuff
462: //if it can be downcast to ServletControllerRequest
463: //
464: HttpServletRequest req = (HttpServletRequest) servRequest
465: .getServletRequest();
466: HttpSession session = req.getSession(true);
467: HttpServletResponse res = (HttpServletResponse) servRequest
468: .getServletResponse();
469: Servlet servlet = servRequest.getCallingServlet();
470:
471: String fileSep = System.getProperty("file.separator");
472: String inputFilename = req.getParameter("filename");
473:
474: try {
475: // Append the input filename to the root directory
476: if (inputFilename == null) {
477: throw new ControllerException(
478: "filename parameter is not set.");
479: }
480:
481: // Append the input filename to the root directory
482: if (inputFilename == null) {
483: throw new ControllerException(
484: "filename parameter is not set.");
485: }
486:
487: //
488: //By running it throuhg the filter, we should be able to remove all
489: //sorts of URL trickery that occurs by people sending urls like a
490: //URL encoded version of:
491: //mail hacker@hotmail.com -s Password! < cat /../../../etc/passwd
492: //(And YES, this kind of attack happens often!)
493: //
494: inputFilename = fileNameFilter.stripFilter(inputFilename);
495:
496: if (inputFilename.length() == 0) {
497: throw new ControllerException(
498: "filename parameter is not set.");
499: }
500:
501: if (!(inputFilename.endsWith(".java"))) {
502: throw new ControllerException(
503: "Sorry, it is forbidden to serve this type of file. This servlet only serves Java source files!");
504: }
505:
506: boolean fileWasServed = false;
507:
508: for (int k = 0; k < rootDirList.size(); ++k) {
509: String rootDir = (String) rootDirList.elementAt(k);
510: File file = new File(rootDir, inputFilename);
511: String filename = file.getPath();
512:
513: if (file.exists()) {
514: if (!file.canRead()) {
515: throw new IOException("not readable filename:`"
516: + inputFilename + "'");
517: }
518: if (file.isDirectory()) {
519: throw new IOException(
520: "cannot serve a directory as filename:`"
521: + inputFilename + "'");
522: }
523:
524: fileWasServed = true; /*!*/
525:
526: //
527: //Set this value to indicate we don't want to do a normal struts forward
528: //
529: response.setCustomResponse(true);
530:
531: // Get the MIME type to return the browser
532: String contentType = "text/html";
533: if (log.isInfoEnabled()) {
534: log.info("JavaFileServeServlet: Serving file:`"
535: + filename + "' type:" + contentType);
536: }
537:
538: // Create a buffer for the file contents
539: ByteArrayOutputStream baos = new ByteArrayOutputStream(
540: 32678);
541: PrintStream prs = new PrintStream(baos);
542:
543: // Set up parameter structure
544: Parameters params = new Parameters();
545: params.req = req;
546: params.res = res;
547: params.session = session;
548: params.inputFilename = inputFilename;
549: params.filename = filename;
550: params.out = prs;
551:
552: // Extract the file contents and format HTML
553: returnHTMLFormattedFile(params);
554: prs.flush(); // MAKE SURE!!!
555:
556: // Provided no I/O exception occurred, pump the buffered
557: // data to the browser now.
558: OutputStream out = res.getOutputStream();
559: res.setContentType(contentType);
560: baos.writeTo(out);
561: out.flush();
562: out.close();
563: break;
564: }
565: }
566: if (!fileWasServed) {
567: throw new FileNotFoundException("no such file: `"
568: + inputFilename + "'");
569: }
570:
571: } catch (FileNotFoundException ex) {
572: log.error("FileNotFoundException locating file file", ex);
573: throw new ControllerException(
574: "FileNotFoundException Error transferring file", ex);
575: } catch (java.io.IOException ioe) {
576: log.error("I/O Error transferring file", ioe);
577: throw new ControllerException(
578: "I/O Error transferring file", ioe);
579: }
580:
581: return response;
582: }
583:
584: public void returnHTMLFormattedFile(Parameters params)
585: throws FileNotFoundException, IOException {
586: writeHeader(params);
587: writeContent(params);
588: writeFooter(params);
589: }
590:
591: public void writeHeader(Parameters params)
592: throws FileNotFoundException, IOException {
593: PrintStream out = params.out;
594: out.println("<html>");
595: out.println("<head>");
596: out.println("<title>Java Source File: `" + params.inputFilename
597: + "'</title>");
598: out.println("<meta name=\"generator\" value=\""
599: + getClass().getName() + "\" >");
600: out.println("<meta name=\"published_date\" value=\""
601: + new Date() + "\" >");
602: out.println("</head>");
603: out.println("<body bgcolor=\"#FFFFFF\" >");
604:
605: File file = new File(params.filename);
606: out
607: .println("<p><font face=\"Lucida, Georgia, Arial,Helvetica\" size=\"-1\" color=\"#000000\" >filename: <b>"
608: + params.inputFilename + "</b><br>");
609: out.println("file size: <b>" + file.length() + "</b><br>");
610: out.println("last modified: <b>"
611: + new Date(file.lastModified()) + "</b><br>");
612: out.println("</font></p>");
613: }
614:
615: protected void writeFooter(Parameters params)
616: throws FileNotFoundException, IOException {
617: PrintStream out = params.out;
618: out.println("</body>");
619: out.println("</html>");
620: }
621:
622: protected void writeContent(Parameters params)
623: throws FileNotFoundException, IOException {
624: PrintStream out = params.out;
625: FileInputStream fis = null;
626: BufferedInputStream bis = null;
627: PushbackInputStream pis = null;
628: boolean insideDQuote = false;
629: boolean insideSQuote = false;
630: boolean cstyleComment = false;
631:
632: try {
633:
634: // Try to open a file stream to the Java source file
635: fis = new FileInputStream(params.filename);
636: bis = new BufferedInputStream(fis, 8192);
637: pis = new PushbackInputStream(bis, 256);
638:
639: // Write start of the content section
640: out.println("<pre>");
641: out
642: .println("<font face=\"Courier New, Monospace, Helvetica, San-serif\" size=\"+0\" color=\"#000000\" >");
643:
644: int c1 = pis.read();
645:
646: while (c1 >= 0) {
647:
648: // Pipe through any blank spaces
649: while (Character.isWhitespace((char) c1)) {
650: out.print((char) c1);
651: c1 = pis.read();
652: }
653: if (c1 < 0) {
654:
655: // EOF - End of file reached
656: break;
657: }
658:
659: if (log.isDebugEnabled()) {
660: System.out.print((char) c1);
661: }
662:
663: if (c1 == '/') {
664: int c2 = pis.read();
665:
666: if (c2 == '/') {
667:
668: // Directly hand 'C++' style comment
669: out.print("<font color=\"" + cplusCommentColor
670: + "\">//");
671:
672: if (log.isDebugEnabled()) {
673: System.out.println("C++ comment");
674: }
675:
676: c2 = pis.read();
677:
678: while (c2 != -1 && c2 != '\n') {
679: out.print(substituteEntity((char) c2));
680: c2 = pis.read();
681: }
682:
683: out.print("</font>\n");
684: c1 = pis.read(); // READ NEXT
685: continue;
686: } else if (c2 == '*') {
687:
688: // Start of 'C' style comment
689: if (log.isDebugEnabled()) {
690: System.out.println("C comment start");
691: }
692: cstyleComment = true;
693: out.print("<font color=\"" + cstyleCommentColor
694: + "\">/*");
695: c1 = pis.read(); // READ NEXT
696: continue;
697: } else {
698: pis.unread(c2);
699: }
700: } else if (c1 == '*') {
701: int c2 = pis.read();
702:
703: if (c2 == '/') {
704:
705: // End of 'C' style comment
706: if (log.isDebugEnabled()) {
707: System.out.println("C comment end"); // FIXME:
708: }
709:
710: cstyleComment = false;
711: out.print("*/</font>");
712: c1 = pis.read(); // READ NEXT
713: continue;
714: } else {
715: pis.unread(c2);
716: }
717: }
718: if (c1 == '\"' && !cstyleComment) {
719: if (!insideDQuote) {
720: out.print("<font color=\"" + doubleQuoteColor
721: + "\">"");
722: } else {
723: out.print(""</font>");
724: }
725:
726: insideDQuote = !insideDQuote;
727: if (log.isDebugEnabled()) {
728: System.out.println("double quotes:"
729: + insideDQuote);
730: }
731: } else if (c1 == '\'' && !cstyleComment
732: && !insideDQuote) {
733: if (!insideSQuote) {
734: out.print("<font color=\"" + singleQuoteColor
735: + "\">'");
736: } else {
737: out.print("'</font>");
738: }
739:
740: insideSQuote = !insideSQuote;
741: if (log.isDebugEnabled()) {
742: System.out.println("single quotes:"
743: + insideSQuote);
744: }
745: } else if (Character.isDigit((char) c1)
746: && !cstyleComment && !insideSQuote
747: && !insideDQuote) {
748:
749: // Handle parsing of decimal or floating
750: // formatting numbers here
751: boolean valid = true;
752: StringBuffer tokenBuffer = new StringBuffer();
753: tokenBuffer.append((char) c1);
754:
755: int c2 = pis.read();
756:
757: while (Character.isDigit((char) c2)) {
758: tokenBuffer.append((char) c2);
759: c2 = pis.read();
760: }
761:
762: pis.mark(256);
763: if (log.isDebugEnabled()) {
764: System.out.println("(1) token=`"
765: + tokenBuffer.toString() + "' (c2:"
766: + c2 + "<" + (char) c2 + ")");
767: }
768:
769: if (c2 == '.') {
770: tokenBuffer.append((char) c2);
771: c2 = pis.read();
772: if (log.isDebugEnabled()) {
773: System.out.println("(2) token=`"
774: + tokenBuffer.toString() + "' (c2:"
775: + c2 + "<" + (char) c2 + ")");
776: }
777:
778: if (c2 == 'e' || c2 == 'E') {
779: tokenBuffer.append((char) c2);
780: c2 = pis.read();
781: if (log.isDebugEnabled()) {
782: System.out.println("(3) token=`"
783: + tokenBuffer.toString()
784: + "' (c2:" + c2 + "<"
785: + (char) c2 + ")");
786: }
787: }
788:
789: System.out.println("(4) token=`"
790: + tokenBuffer.toString() + "' (c2:"
791: + c2 + "<" + (char) c2 + ")");
792:
793: if (c2 == '+' || c2 == '-') {
794: tokenBuffer.append((char) c2);
795: c2 = pis.read();
796: }
797: if (Character.isDigit((char) c2)) {
798: while (Character.isDigit((char) c2)) {
799: tokenBuffer.append((char) c2);
800: c2 = pis.read();
801: }
802: } else {
803:
804: // Sorry, not a valid floating point number
805: // `[0-9]+\.(E|e)?(+|-)?[0-9]+'
806: valid = false;
807: pis.reset();
808: }
809: }
810:
811: pis.unread(c2); // Pushback the last unread!
812:
813: if (valid) {
814: out.print("<font color=\"" + decimalNumberColor
815: + "\" >");
816: }
817:
818: out.print(tokenBuffer.toString());
819:
820: if (valid) {
821: out.print("</font>");
822: }
823: } else if (Character.isLetter((char) c1)
824: && !cstyleComment && !insideSQuote
825: && !insideDQuote) {
826:
827: // Handle reserved keywords here
828: StringBuffer tokenBuffer = new StringBuffer();
829: tokenBuffer.append((char) c1);
830:
831: int c2 = pis.read();
832:
833: while (Character.isLetter((char) c2)) {
834: tokenBuffer.append((char) c2);
835: c2 = pis.read();
836: }
837:
838: pis.unread(c2); // Pushback the last unread!
839:
840: // Does the token match a reserved Java keyword?
841: boolean wasMatched = false;
842: String token = tokenBuffer.toString();
843: if (log.isDebugEnabled()) {
844: System.out.println("<" + token + ">(" + c2
845: + "/`" + (char) c2 + "')");
846: }
847:
848: ReservedWord reservedWord = (ReservedWord) fast_keyword_map
849: .get(token);
850:
851: if (reservedWord != null) {
852:
853: // Matched a reserved Java keyword
854: wasMatched = true;
855: }
856: if (wasMatched) {
857: out.print("<font color=\""
858: + reservedWord.htmlcolor + "\" >");
859: }
860:
861: out.print(token);
862:
863: if (wasMatched) {
864: out.print("</font>");
865: }
866: } else {
867: out.print(substituteEntity((char) c1));
868: }
869:
870: // DON'T FORGET: Read the next character from the pushback stream!
871: c1 = pis.read();
872: }
873:
874: // Write end of the content section
875: out.println("</font>");
876: out.println("</pre>");
877: } finally {
878: if (pis != null) {
879: pis.close();
880: }
881: if (fis != null) {
882: fis.close();
883: }
884: }
885: }
886:
887: protected static final String substituteEntity(char c9) {
888: switch (c9) {
889: case '<':
890: return "<";
891:
892: case '>':
893: return ">";
894:
895: case '&':
896: return "&";
897:
898: case '\"':
899: return """;
900: }
901:
902: return new String(new char[] { c9 });
903: }
904:
905: public String getTitle() {
906: return "Serve Text File";
907: }
908:
909: }
|