001: /*
002: * Enhydra Java Application Server Project
003: *
004: * The contents of this file are subject to the Enhydra Public License
005: * Version 1.1 (the "License"); you may not use this file except in
006: * compliance with the License. You may obtain a copy of the License on
007: * the Enhydra web site ( http://www.enhydra.org/ ).
008: *
009: * Software distributed under the License is distributed on an "AS IS"
010: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
011: * the License for the specific terms governing rights and limitations
012: * under the License.
013: *
014: * The Initial Developer of the Enhydra Application Server is Lutris
015: * Technologies, Inc. The Enhydra Application Server and portions created
016: * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
017: * All Rights Reserved.
018: *
019: * Contributor(s):
020: *
021: * $Id: FileServerServlet.java,v 1.2 2006-06-15 14:07:00 sinisa Exp $
022: */
023:
024: package org.enhydra.servlet.servlets;
025:
026: import java.io.File;
027: import java.io.FileInputStream;
028: import java.io.IOException;
029: import java.io.InputStream;
030: import java.io.OutputStream;
031:
032: import javax.servlet.ServletConfig;
033: import javax.servlet.ServletContext;
034: import javax.servlet.ServletException;
035: import javax.servlet.ServletOutputStream;
036: import javax.servlet.http.HttpServlet;
037: import javax.servlet.http.HttpServletRequest;
038: import javax.servlet.http.HttpServletResponse;
039:
040: import com.lutris.http.MimeType;
041: import com.lutris.logging.LogChannel;
042: import com.lutris.logging.Logger;
043: import com.lutris.logging.StandardLogger;
044:
045: //SV import org.enhydra.servlet.connectionMethods.http.*;
046: //SV import org.enhydra.servlet.servletManager.EnhydraServletContext;
047:
048: /**
049: * This class serves files from HTTP request. Currently only a
050: * limited Http GET request will be processed. CGI arguments will
051: * not be processed. The initial parameter 'index=something.html' can
052: * for the default html page. If this parameter is not set then it
053: * will default to "index.html".
054: *
055: * @version 1.8, 3/98
056: * @auther Mark Sanguinetti
057: */
058:
059: public class FileServerServlet extends HttpServlet {
060:
061: /**
062: * Buffer size for writeing one stream to another.
063: */
064: private static final int buffer_size = 1024 * 1;
065:
066: /**
067: * Html text to send clent for a file not found.
068: */
069: private final static String NOT_FOUND_MSG = "<TITLE>Not Found</TITLE><H1>Not Found</H1> "
070: + "The requested object does not exist on this Enhydra Server. "
071: + "The link you followed is either outdated, inaccurate, "
072: + "or the server has been instructed not to let you have it.";
073:
074: /**
075: * Html text to send client when Access to file denied.
076: */
077: private final static String ACCESS_DENIED_MSG = "<TITLE>Access Denied</TITLE><H1>Access Denied</H1> "
078: + "Access is not allowed to the requested file. ";
079:
080: /**
081: *
082: */
083: private final static String SC_MOVED_PERMANENTLY_MSG = "<TITLE>Moved Permanently</TITLE><H1>Moved Permanently</H1> "
084: + "Resouce has been moved";
085:
086: /**
087: * Default html page.
088: */
089: private String defaultPage = null;
090:
091: /**
092: * The logging channel.
093: */
094: private LogChannel logChannel;
095:
096: /*
097: * If we create the central logger, we save a copy of it for later
098: * initialization. If we didn't create it, this will be null.
099: */
100: private StandardLogger standardLogger;
101:
102: /*
103: * If we create the central logger, we will need to initialize it later
104: * when init() is called.
105: */
106: private boolean needToConfigureLogChannel = false;
107:
108: /**
109: * The Sevelet context.
110: */
111: private ServletContext myContext;
112:
113: /**
114: * The document root for the file server and is
115: * specified in servlet context.
116: */
117: private Path docRoot = null;
118:
119: /**
120: * Number of file served by this servlet.
121: */
122: private long numServed = 0;
123:
124: /**
125: * Class for determing the mime type of a file
126: */
127: private MimeType mimetypes = new MimeType();
128:
129: /**
130: * Mime type extension for executing a cgi script in thr Doc Root.
131: */
132: private String cgiType = null;
133:
134: /**
135: * Create a new FileServerServlet.
136: */
137: public FileServerServlet() {
138: super ();
139: if (Logger.getCentralLogger() == null) {
140: standardLogger = new StandardLogger(true);
141: needToConfigureLogChannel = true;
142: }
143: logChannel = Logger.getCentralLogger().getChannel("Enhydra");
144: }
145:
146: /**
147: * This initializes the servlet and sets the variables docRoot and
148: * log logging options.
149: *
150: * @param config The servlet configuration object.
151: * @exception If an exception occured during initialztion
152: */
153: public void init(ServletConfig config) throws ServletException {
154: super .init(config);
155: myContext = config.getServletContext();
156:
157: // FIX: check for docRoot == null
158: docRoot = new Path(myContext.getRealPath(""));
159:
160: // get defaultPage form parameters
161: defaultPage = config.getInitParameter("index");
162: if (defaultPage == null) {
163: defaultPage = "index.html";
164: }
165:
166: // cgi extension, if any.
167: cgiType = config.getInitParameter("cgi");
168:
169: //Try to get the logging channel, if we are being run under
170: // the MultiServer.
171: //SV if (myContext instanceof
172: //SV org.enhydra.servlet.servletManager.EnhydraServletContext) {
173: //SV EnhydraServletContext lbsContext =
174: //SV (EnhydraServletContext) myContext;
175: //SV logChannel = lbsContext.getLogChannel();
176: //SV }
177: logInfo("Starting File Server Servlet with docRoot "
178: + docRoot.getFilePath());
179: }
180:
181: /**
182: * This method will process a limited http GET request for a file.
183: * The method will serve files, but will not process cgi arguments.
184: *
185: * @param req encapsulates the request to the servlet.
186: * @param res encapsulates the response from the servlet.
187: * @exception if the request could not be handled.
188: * @exception if detected when processing.
189: */
190: public void doGet(HttpServletRequest req, HttpServletResponse res)
191: throws ServletException, IOException {
192:
193: /* FIX: Path object is used because the file object doesn't
194: * fix multiple file separator char and also problems with
195: * converting URL path to a File path with platform specific
196: * file separator char.
197: */
198:
199: String pathInfo = req.getPathInfo();
200: String path = docRoot.appendFilePath(pathInfo);
201: File file = new File(path);
202: if (isAuth(file.getPath())) {
203: // check for cgi
204: if (cgiType != null) {
205: String scriptPath = getScriptPath(path);
206: if (scriptPath != null) {
207: // process cgi
208: String scriptPathInfo = getScriptPathInfo(path,
209: scriptPath);
210: String scriptName = getScriptName(docRoot
211: .getUrlPath(), scriptPath);
212: CgiProcessor cgi = new CgiProcessor();
213: cgi.processCgiRequest(req, res, scriptPath,
214: scriptPathInfo, scriptName);
215: } else {
216: // process file request
217: processFileRequest(file, req, res);
218: }
219: } else {
220: // process file request
221: processFileRequest(file, req, res);
222: }
223: } else {
224: // path containing '..' is not authorized
225: res.sendError(res.SC_NOT_FOUND, NOT_FOUND_MSG);
226: logInfo("FileServerServlet: File " + file + " not Found.");
227: }
228: }
229:
230: /**
231: *
232: */
233: private String getScriptName(String docRoot, String scriptPath) {
234:
235: return scriptPath.substring(docRoot.length() + 1);
236: }
237:
238: /**
239: *
240: */
241: private String getScriptPathInfo(String path, String scriptPath) {
242:
243: int index = path.indexOf(scriptPath);
244: if (index == -1) {
245: return "";
246: }
247: return path.substring(index + scriptPath.length());
248: }
249:
250: /**
251: *
252: */
253: private String getScriptPath(String path) {
254:
255: String cgi = "." + cgiType;
256: int index = path.indexOf(cgi);
257: if (index == -1) {
258: return null;
259: }
260: return path.substring(0, index + cgi.length());
261: }
262:
263: /**
264: *
265: */
266: private void processFileRequest(File file, HttpServletRequest req,
267: HttpServletResponse res) throws IOException {
268:
269: if (file.exists()) {
270: // file exists
271: if (file.isDirectory()) {
272: // send dir. index page of list dir.
273: processDirectory(req, res);
274: } else {
275: // send requested file
276: sendPage(file, res);
277: }
278: } else {
279: // file doesn't exist
280: res.sendError(res.SC_NOT_FOUND, NOT_FOUND_MSG);
281: logInfo("FileServerServlet: File " + file + " not Found.");
282: }
283: }
284:
285: /**
286: *
287: */
288: private void processDirectory(HttpServletRequest req,
289: HttpServletResponse res) throws IOException {
290:
291: String pathInfo = req.getPathInfo();
292: if (pathInfo == null) {
293: pathInfo = "/";
294: }
295:
296: Path path = new Path(docRoot.getUrlPath(), pathInfo);
297: if (pathInfo.endsWith("/")) {
298: String indexPath = path.appendFilePath(defaultPage);
299: File indexFile = new File(indexPath);
300: if (indexFile.exists()) {
301: // send index file
302: sendPage(indexFile, res);
303: } else {
304: // list directory
305: File dir = new File(path.getFilePath());
306: listDirectory(dir, req, res);
307: }
308: } else {
309: sendDirectoryRedirect(req, res);
310: }
311: }
312:
313: /**
314: *
315: */
316: private void sendDirectoryRedirect(HttpServletRequest req,
317: HttpServletResponse res) throws IOException {
318:
319: String location = req.getRequestURI() + "/";
320: res.setHeader("Location", location);
321: res.sendError(res.SC_MOVED_PERMANENTLY,
322: SC_MOVED_PERMANENTLY_MSG);
323: }
324:
325: /**
326: * Reconstruct the URL for this request.
327: *
328: * @param request to the servlet.
329: * @return url.
330: */
331: String getUrl(HttpServletRequest request) {
332:
333: String host = request.getScheme() + "://";
334: host += request.getServerName();
335: int port;
336: if ((port = request.getServerPort()) != 80) {
337: host += ":" + port;
338: }
339: return request.getRequestURI();
340: }
341:
342: /**
343: * Generates an html listing of a directory.
344: *
345: * FIX: should create a temp file, set a content-length aheadr
346: * and output like a normal file.
347: *
348: * @param root Directory of interest.
349: * @param req
350: * @param res
351: */
352: private void listDirectory(File root, HttpServletRequest req,
353: HttpServletResponse res) throws IOException {
354:
355: res.setContentType("text/html");
356: ServletOutputStream out = res.getOutputStream();
357:
358: String vPath = (req.getPathInfo() == null ? "/" : req
359: .getPathInfo());
360: String vParent = (vPath.equals("/") ? "" : "../");
361: out.println("<html>");
362: out.println("<head><title>Index of " + vPath
363: + "</title></head>");
364: out.println("<body>");
365: out.println("<h1>Index of " + vPath + "</h1>\n<hr>");
366: out.println("<pre>");
367: out.println("<a href=\"" + vParent + "\">Parent Directory</a>");
368:
369: String href = null;
370: String files[] = root.list();
371: String path = root.getPath();
372: for (int i = 0; i < files.length; i++) {
373: File f = new File(path + "/" + files[i]);
374: if (f.isDirectory()) {
375: href = "<a href=\"" + files[i] + "/\">" + files[i]
376: + "</a>";
377: } else {
378: href = "<a href=\"" + files[i] + "\">" + files[i]
379: + "</a>";
380: }
381: out.println(href);
382: }
383: out.println("</pre>");
384: out.println("</body>");
385: out.println("</html>");
386: }
387:
388: /**
389: * Test if is authorized. A path is authorized if
390: * it does not contain a '..'. The browser should resolve
391: * the '..'.
392: *
393: * @param path requested path
394: * @return true if path is authorized
395: */
396: private boolean isAuth(String path) {
397:
398: if (path.indexOf("..") == -1) {
399: return true;
400: }
401: return false;
402: }
403:
404: /**
405: *
406: *
407: */
408: private void sendPage(File file, HttpServletResponse res)
409: throws IOException {
410:
411: String filetype = mimetypes.getType(file.getName());
412: res.setContentType(filetype);
413: Long len = new Long(file.length());
414: res.setHeader("Content-Length", len.toString());
415: ServletOutputStream out = res.getOutputStream();
416: FileInputStream in = new FileInputStream(file);
417: writeFile(out, in);
418: in.close();
419: numServed++;
420: }
421:
422: /**
423: * Http POST command is currently not supported and indicates
424: * this sending a message to the client.
425: *
426: * @param req encapsulates the request to the servlet.
427: * @param res encapsulates the response from the servlet.
428: * @exception if the request could not be handled.
429: * @exception if detected when processing.
430: */
431: public void doPost(HttpServletRequest req, HttpServletResponse res)
432: throws ServletException, IOException {
433: res.sendError(res.SC_NOT_IMPLEMENTED);
434: logInfo("FileServerServlet: Access Denied.");
435: }
436:
437: /**
438: * Returns a string containing servlet infoemation including name,
439: * and docRoot.
440: *
441: * @return Servlet informatioon.
442: */
443: public String getServletInfo() {
444: return "A FileServerServlet, with doc root "
445: + docRoot.getFilePath();
446: }
447:
448: /**
449: * Extra status info, shown by the Enhydra Server admin status page.
450: */
451: public String toHtml() {
452: return "Serving files from the directory "
453: + docRoot.getFilePath() + ".<BR>\n" + numServed
454: + " files served.";
455: }
456:
457: /**
458: * Transfers to one stream to another via a buffer. In this case
459: * the input stream is a file to be transferred and the output
460: * stream is servlet output stream.
461: *
462: * @param out The destination stream.
463: * @param in The source stream.
464: * @exception if an error occured during IO operations.
465: */
466: private void writeFile(OutputStream out, InputStream in)
467: throws IOException {
468:
469: byte buffer[] = new byte[buffer_size];
470: while (true) {
471: int n = in.read(buffer);
472: if (n == -1)
473: break;
474: out.write(buffer, 0, n);
475: }
476: }
477:
478: /**
479: * Write standard (non error) information to the log. If logChannel
480: * is not null then the information will be logged to the lbs
481: * logged channel defined in lbsContext otherwise the information
482: * will be logged to the standard servlet log.
483: *
484: * @param msg The message to be logged.
485: */
486: private void logInfo(String msg) {
487: // v. strahinja, 01 okt 2002
488: if (logChannel != null)
489: // if (logger != null)
490: // v. strahinja, 26 sep 2002
491: logChannel.write(Logger.INFO, msg);
492: // logger.log(Level.INFO, msg);
493: else
494: myContext.log(msg);
495: }
496:
497: /**
498: * Write error information to the log. If logChannel is not null
499: * then the information will be logged to the lbs logged channel
500: * defined in lbsContext otherwise the information will be logged to
501: * the standard servlet log.
502: *
503: *@param err The error message to be logged.
504: *@param e The exception to be logged.
505: */
506: private void logError(String err, Exception e) {
507: // v. strahinja, 01 okt 2002
508: if (logChannel != null)
509: // if (logger != null)
510: // v. strahinja, 26 sep 2002
511: logChannel.write(Logger.ERROR, err, e);
512: // logger.log(Level.ERROR, err, e);
513: else
514: myContext.log(e, err);
515: }
516: }
|