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: JarServerServlet.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.IOException;
027: import java.io.InputStream;
028: import java.io.OutputStream;
029: import java.util.StringTokenizer;
030: import java.util.zip.ZipEntry;
031: import java.util.zip.ZipFile;
032:
033: import javax.servlet.ServletConfig;
034: import javax.servlet.ServletContext;
035: import javax.servlet.ServletException;
036: import javax.servlet.ServletOutputStream;
037: import javax.servlet.http.HttpServlet;
038: import javax.servlet.http.HttpServletRequest;
039: import javax.servlet.http.HttpServletResponse;
040:
041: import com.lutris.http.MimeType;
042: import com.lutris.logging.LogChannel;
043: import com.lutris.logging.Logger;
044: import com.lutris.logging.StandardLogger;
045:
046: //SV import org.enhydra.servlet.connectionMethods.http.*;
047: //SV import org.enhydra.servlet.servletManager.EnhydraServletContext;
048:
049: /**
050: * This class serves files from jar/zip files for HTTP requests.
051: * Currently only a
052: * limited Http GET request will be processed. CGI arguments will
053: * not be processed. The initArg "jarFile" sets the name of the jar
054: * or zip file to serve files from.
055: *
056: * @version $Revision: 1.2 $
057: * @auther Mark Sanguinetti
058: * @auther Shawn McMurdo
059: */
060: public class JarServerServlet extends HttpServlet {
061:
062: /**
063: * Buffer size for writing one stream to another.
064: */
065: private static final int buffer_size = 1024 * 1;
066:
067: /**
068: * Html text to send client for a file not found.
069: */
070: private final static String NOT_FOUND_MSG = "<TITLE>Not Found</TITLE><H1>Not Found</H1> "
071: + "The requested object does not exist on this Enhydra Server. "
072: + "The link you followed is either outdated, inaccurate, "
073: + "or the server has been instructed not to let you have it.";
074:
075: /**
076: * Html text to send client when Access to file denied.
077: */
078: private final static String ACCESS_DENIED_MSG = "<TITLE>Access Denied</TITLE><H1>Access Denied</H1> "
079: + "Access is not allowed to the requested file. ";
080:
081: /**
082: *
083: */
084: private final static String SC_MOVED_PERMANENTLY_MSG = "<TITLE>Moved Permanently</TITLE><H1>Moved Permanently</H1> "
085: + "Resource has been moved";
086:
087: /**
088: * The logging channel.
089: */
090: private LogChannel logChannel;
091:
092: /*
093: * If we create the central logger, we save a copy of it for later
094: * initialization. If we didn't create it, this will be null.
095: */
096: private StandardLogger standardLogger;
097:
098: /*
099: * If we create the central logger, we will need to initialize it later
100: * when init() is called.
101: */
102: private boolean needToConfigureLogChannel = false;
103:
104: /**
105: * The Servlet context.
106: */
107: private ServletContext myContext;
108:
109: /**
110: * The jar file for the file server. It is
111: * specified in the servlet initArgs.
112: */
113: private String jarFileName = null;
114: private ZipFile jarFile = null;
115: /**
116: * The default inner file for the file server. It is
117: * specified in the servlet initArgs.
118: */
119: private String defaultInner = null;
120:
121: /**
122: * Number of files served by this servlet.
123: */
124: private long numServed = 0;
125:
126: /**
127: * Class for determining the mime type of a file
128: */
129: private MimeType mimetypes = new MimeType();
130:
131: //SV enabling relative referencing to jar file
132: /**
133: * The document root for the jar server
134: * specified in servlet context.
135: */
136: private Path docRoot = null;
137:
138: /**
139: * Create a new JarServerServlet.
140: */
141: public JarServerServlet() {
142: super ();
143: if (Logger.getCentralLogger() == null) {
144: standardLogger = new StandardLogger(true);
145: needToConfigureLogChannel = true;
146: }
147: logChannel = Logger.getCentralLogger().getChannel("Enhydra");
148: }
149:
150: /**
151: * This initializes the servlet and gets the jarFile parameter.
152: *
153: * @param config The servlet configuration object.
154: * @exception If an exception occured during initialztion
155: */
156: public void init(ServletConfig config) throws ServletException {
157: super .init(config);
158: myContext = config.getServletContext();
159:
160: jarFileName = config.getInitParameter("jarFile");
161: defaultInner = config.getInitParameter("default");
162:
163: //SV adition with pourpose to enable locating jar file
164: // relatively to DocRoot
165: String docRoot = myContext.getRealPath("");
166:
167: // covert to internal represenation of a path ('/' format)
168: String tmp = docRoot.replace('\\', '/');
169: if (!tmp.startsWith("/")) {
170: tmp = "/" + tmp;
171: }
172: if (!tmp.endsWith("/")) {
173: tmp = tmp + "/";
174: }
175:
176: // parse out info in path and create a cleaned up
177: // url path
178: StringBuffer sb = new StringBuffer();
179: StringTokenizer st = new StringTokenizer(tmp, "/");
180: sb.append("/");
181: while (st.hasMoreTokens()) {
182: String tok = st.nextToken();
183: if (st.countTokens() > 0) {
184: sb.append(tok);
185: sb.append("/");
186: } else if (st.countTokens() == 0) {
187: sb.append(tok);
188: } else {
189: // should never happen
190: System.err
191: .println("ERROR: no able to determine DocRoot");
192: }
193: }
194: docRoot = sb.toString();
195: try {
196: jarFile = new ZipFile(jarFileName);
197: } catch (IOException ioe) {
198: try {
199: jarFileName = docRoot + "/" + jarFileName;
200: jarFile = new ZipFile(jarFileName);
201: } catch (IOException ioex) {
202: throw new ServletException(ioe.getMessage());
203: }
204: }
205:
206: //Try to get the logging channel, if we are being run under
207: // the MultiServer.
208: //SV if (myContext instanceof EnhydraServletContext) {
209: //SV EnhydraServletContext lbsContext =
210: //SV (EnhydraServletContext) myContext;
211: //SV logChannel = lbsContext.getLogChannel();
212: //SV }
213: logInfo("Starting Jar Server Servlet with jarFile "
214: + jarFileName);
215: }
216:
217: /**
218: * This method will process a limited http GET request for a file.
219: * The method will serve files, but will not process cgi arguments.
220: *
221: * @param req encapsulates the request to the servlet.
222: * @param res encapsulates the response from the servlet.
223: * @exception if the request could not be handled.
224: * @exception if detected when processing.
225: */
226: public void doGet(HttpServletRequest req, HttpServletResponse res)
227: throws ServletException, IOException {
228:
229: String pathInfo = req.getPathInfo();
230:
231: /*SV bug fix for null option
232: * if no iner file specified sevlet will present default one
233: */
234: if (pathInfo == null || pathInfo.equals("")
235: || pathInfo.equals("/")) {
236: pathInfo = defaultInner;
237: }
238:
239: /*SV bug fix
240: * path starting with '/' is not authorized
241: * ZipEntry would not be created in that case
242: */
243: if (pathInfo.startsWith("/")) {
244: pathInfo = pathInfo.substring(1);
245: }
246:
247: ZipEntry jarEntry = jarFile.getEntry(pathInfo);
248: if (isAuth(pathInfo)) {
249: processFileRequest(jarEntry, req, res);
250: } else {
251: // path containing '..' is not authorized
252: res.sendError(res.SC_NOT_FOUND, NOT_FOUND_MSG);
253: logInfo("JarServerServlet: File " + pathInfo
254: + " not Found.");
255: }
256: }
257:
258: /**
259: *
260: */
261: private void processFileRequest(ZipEntry file,
262: HttpServletRequest req, HttpServletResponse res)
263: throws IOException {
264:
265: if (file != null) {
266: // file exists
267: if (file.isDirectory()) {
268: // don't do directories
269: res.sendError(res.SC_NOT_FOUND, NOT_FOUND_MSG);
270: logInfo("JarServerServlet: File " + file
271: + " not Found.");
272: } else {
273: // send requested file
274: sendPage(file, res);
275: }
276: } else {
277: // file doesn't exist
278: res.sendError(res.SC_NOT_FOUND, NOT_FOUND_MSG);
279: logInfo("JarServerServlet: File " + file + " not Found.");
280: }
281: }
282:
283: /**
284: * Test if path is authorized. A path is authorized if
285: * it does not contain a '..'. The browser should resolve
286: * the '..'.
287: *
288: * @param path requested path
289: * @return true if path is authorized
290: */
291: private boolean isAuth(String path) {
292:
293: if (path.indexOf("..") == -1) {
294: return true;
295: }
296: return false;
297: }
298:
299: /**
300: *
301: *
302: */
303: private void sendPage(ZipEntry file, HttpServletResponse res)
304: throws IOException {
305:
306: String filetype = mimetypes.getType(file.getName());
307: res.setContentType(filetype);
308: Long len = new Long(file.getSize());
309: res.setHeader("Content-Length", len.toString());
310: ServletOutputStream out = res.getOutputStream();
311: InputStream in = jarFile.getInputStream(file);
312: writeFile(out, in);
313: in.close();
314: numServed++;
315: }
316:
317: /**
318: * Http POST command is currently not supported and indicates
319: * this sending a message to the client.
320: *
321: * @param req encapsulates the request to the servlet.
322: * @param res encapsulates the response from the servlet.
323: * @exception if the request could not be handled.
324: * @exception if detected when processing.
325: */
326: public void doPost(HttpServletRequest req, HttpServletResponse res)
327: throws ServletException, IOException {
328: res.sendError(res.SC_NOT_IMPLEMENTED);
329: logInfo("JarServerServlet: Access Denied.");
330: }
331:
332: /**
333: * Returns a string containing servlet information including name,
334: * and jarFile.
335: *
336: * @return Servlet information.
337: */
338: public String getServletInfo() {
339: return "A JarServerServlet, with jar file " + jarFileName;
340: }
341:
342: /**
343: * Extra status info, shown by the Enhydra Server admin status page.
344: */
345: public String toHtml() {
346: return "Serving files from the jar " + jarFileName + ".<BR>\n"
347: + numServed + " files served.";
348: }
349:
350: /**
351: * Transfers from one stream to another via a buffer. In this case
352: * the input stream is a file to be transferred and the output
353: * stream is servlet output stream.
354: *
355: * @param out The destination stream.
356: * @param in The source stream.
357: * @exception if an error occured during IO operations.
358: */
359: private void writeFile(OutputStream out, InputStream in)
360: throws IOException {
361:
362: byte buffer[] = new byte[buffer_size];
363: while (true) {
364: int n = in.read(buffer);
365: if (n == -1)
366: break;
367: out.write(buffer, 0, n);
368: }
369: }
370:
371: /**
372: * Write standard (non error) information to the log. If logChannel
373: * is not null then the information will be logged to the server
374: * log channel otherwise the information
375: * will be logged to the standard servlet log.
376: *
377: * @param msg The message to be logged.
378: */
379: private void logInfo(String msg) {
380: if (logChannel != null) {
381: logChannel.write(Logger.INFO, msg);
382: } else {
383: myContext.log(msg);
384: }
385: }
386:
387: /**
388: * Write error information to the log. If logChannel is not null
389: * then the information will be logged to the server log channel
390: * otherwise the information will be logged to
391: * the standard servlet log.
392: *
393: * @param err The error message to be logged.
394: * @param e The exception to be logged.
395: */
396: private void logError(String err, Exception e) {
397: if (logChannel != null) {
398: logChannel.write(Logger.ERROR, err, e);
399: } else {
400: myContext.log(e, err);
401: }
402: }
403: }
|