0001: /*
0002: * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/servlets/DefaultServlet.java,v 1.57 2002/06/14 03:19:43 remm Exp $
0003: * $Revision: 1.57 $
0004: * $Date: 2002/06/14 03:19:43 $
0005: *
0006: * ====================================================================
0007: *
0008: * The Apache Software License, Version 1.1
0009: *
0010: * Copyright (c) 1999 The Apache Software Foundation. All rights
0011: * reserved.
0012: *
0013: * Redistribution and use in source and binary forms, with or without
0014: * modification, are permitted provided that the following conditions
0015: * are met:
0016: *
0017: * 1. Redistributions of source code must retain the above copyright
0018: * notice, this list of conditions and the following disclaimer.
0019: *
0020: * 2. Redistributions in binary form must reproduce the above copyright
0021: * notice, this list of conditions and the following disclaimer in
0022: * the documentation and/or other materials provided with the
0023: * distribution.
0024: *
0025: * 3. The end-user documentation included with the redistribution, if
0026: * any, must include the following acknowlegement:
0027: * "This product includes software developed by the
0028: * Apache Software Foundation (http://www.apache.org/)."
0029: * Alternately, this acknowlegement may appear in the software itself,
0030: * if and wherever such third-party acknowlegements normally appear.
0031: *
0032: * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
0033: * Foundation" must not be used to endorse or promote products derived
0034: * from this software without prior written permission. For written
0035: * permission, please contact apache@apache.org.
0036: *
0037: * 5. Products derived from this software may not be called "Apache"
0038: * nor may "Apache" appear in their names without prior written
0039: * permission of the Apache Group.
0040: *
0041: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0042: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0043: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0044: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
0045: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0046: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0047: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0048: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0049: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0050: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0051: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0052: * SUCH DAMAGE.
0053: * ====================================================================
0054: *
0055: * This software consists of voluntary contributions made by many
0056: * individuals on behalf of the Apache Software Foundation. For more
0057: * information on the Apache Software Foundation, please see
0058: * <http://www.apache.org/>.
0059: *
0060: * [Additional notices, if required by prior licensing conditions]
0061: *
0062: */
0063:
0064: package org.apache.catalina.servlets;
0065:
0066: import java.io.BufferedInputStream;
0067: import java.io.ByteArrayOutputStream;
0068: import java.io.ByteArrayInputStream;
0069: import java.io.File;
0070: import java.io.FileInputStream;
0071: import java.io.InputStream;
0072: import java.io.IOException;
0073: import java.io.PrintWriter;
0074: import java.io.RandomAccessFile;
0075: import java.io.Reader;
0076: import java.io.InputStreamReader;
0077: import java.io.Writer;
0078: import java.io.OutputStreamWriter;
0079: import java.net.MalformedURLException;
0080: import java.net.URL;
0081: import java.sql.Timestamp;
0082: import java.util.Date;
0083: import java.util.Enumeration;
0084: import java.util.Vector;
0085: import java.util.StringTokenizer;
0086: import java.util.Locale;
0087: import java.util.TimeZone;
0088: import java.util.Hashtable;
0089: import java.text.ParseException;
0090: import java.text.SimpleDateFormat;
0091: import java.security.MessageDigest;
0092: import java.security.NoSuchAlgorithmException;
0093: import javax.servlet.RequestDispatcher;
0094: import javax.servlet.ServletException;
0095: import javax.servlet.ServletContext;
0096: import javax.servlet.ServletOutputStream;
0097: import javax.servlet.http.HttpServlet;
0098: import javax.servlet.http.HttpServletRequest;
0099: import javax.servlet.http.HttpServletResponse;
0100: import javax.naming.NamingException;
0101: import javax.naming.InitialContext;
0102: import javax.naming.Context;
0103: import javax.naming.NamingEnumeration;
0104: import javax.naming.NameClassPair;
0105: import javax.naming.directory.DirContext;
0106: import javax.naming.directory.Attribute;
0107: import javax.naming.directory.Attributes;
0108: import org.apache.naming.resources.Resource;
0109: import org.apache.naming.resources.ResourceAttributes;
0110: import org.apache.catalina.Globals;
0111: import org.apache.catalina.util.FastHttpDateFormat;
0112: import org.apache.catalina.util.MD5Encoder;
0113: import org.apache.catalina.util.RequestUtil;
0114: import org.apache.catalina.util.ServerInfo;
0115: import org.apache.catalina.util.StringManager;
0116: import org.apache.catalina.util.URLEncoder;
0117:
0118: /**
0119: * The default resource-serving servlet for most web applications,
0120: * used to serve static resources such as HTML pages and images.
0121: *
0122: * @author Craig R. McClanahan
0123: * @author Remy Maucherat
0124: * @version $Revision: 1.57 $ $Date: 2002/06/14 03:19:43 $
0125: */
0126:
0127: public class DefaultServlet extends HttpServlet {
0128:
0129: // ----------------------------------------------------- Instance Variables
0130:
0131: /**
0132: * The debugging detail level for this servlet.
0133: */
0134: protected int debug = 0;
0135:
0136: /**
0137: * The input buffer size to use when serving resources.
0138: */
0139: protected int input = 2048;
0140:
0141: /**
0142: * Should we generate directory listings when no welcome file is present?
0143: */
0144: protected boolean listings = true;
0145:
0146: /**
0147: * Read only flag. By default, it's set to true.
0148: */
0149: protected boolean readOnly = true;
0150:
0151: /**
0152: * The output buffer size to use when serving resources.
0153: */
0154: protected int output = 2048;
0155:
0156: /**
0157: * The set of welcome files for this web application
0158: */
0159: protected String welcomes[] = new String[0];
0160:
0161: /**
0162: * MD5 message digest provider.
0163: */
0164: protected static MessageDigest md5Helper;
0165:
0166: /**
0167: * The MD5 helper object for this class.
0168: */
0169: protected static final MD5Encoder md5Encoder = new MD5Encoder();
0170:
0171: /**
0172: * The set of SimpleDateFormat formats to use in getDateHeader().
0173: */
0174: protected static final SimpleDateFormat formats[] = {
0175: new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz",
0176: Locale.US),
0177: new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz",
0178: Locale.US),
0179: new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) };
0180:
0181: protected final static TimeZone gmtZone = TimeZone
0182: .getTimeZone("GMT");
0183:
0184: /**
0185: * Array containing the safe characters set.
0186: */
0187: protected static URLEncoder urlEncoder;
0188:
0189: /**
0190: * GMT timezone - all HTTP dates are on GMT
0191: */
0192: // ----------------------------------------------------- Static Initializer
0193: static {
0194: formats[0].setTimeZone(gmtZone);
0195: formats[1].setTimeZone(gmtZone);
0196: formats[2].setTimeZone(gmtZone);
0197:
0198: urlEncoder = new URLEncoder();
0199: urlEncoder.addSafeCharacter('-');
0200: urlEncoder.addSafeCharacter('_');
0201: urlEncoder.addSafeCharacter('.');
0202: urlEncoder.addSafeCharacter('*');
0203: urlEncoder.addSafeCharacter('/');
0204: }
0205:
0206: /**
0207: * MIME multipart separation string
0208: */
0209: protected static final String mimeSeparation = "CATALINA_MIME_BOUNDARY";
0210:
0211: /**
0212: * JNDI resources name.
0213: */
0214: protected static final String RESOURCES_JNDI_NAME = "java:/comp/Resources";
0215:
0216: /**
0217: * The string manager for this package.
0218: */
0219: protected static StringManager sm = StringManager
0220: .getManager(Constants.Package);
0221:
0222: /**
0223: * Size of file transfer buffer in bytes.
0224: */
0225: private static final int BUFFER_SIZE = 4096;
0226:
0227: // --------------------------------------------------------- Public Methods
0228:
0229: /**
0230: * Finalize this servlet.
0231: */
0232: public void destroy() {
0233:
0234: ; // No actions necessary
0235:
0236: }
0237:
0238: /**
0239: * Initialize this servlet.
0240: */
0241: public void init() throws ServletException {
0242: log("DefaultServlet.init: start");
0243: // Set our properties from the initialization parameters
0244: String value = null;
0245: try {
0246: value = getServletConfig().getInitParameter("debug");
0247: debug = Integer.parseInt(value);
0248: } catch (Throwable t) {
0249: ;
0250: }
0251: try {
0252: value = getServletConfig().getInitParameter("input");
0253: input = Integer.parseInt(value);
0254: } catch (Throwable t) {
0255: ;
0256: }
0257: try {
0258: value = getServletConfig().getInitParameter("listings");
0259: listings = (new Boolean(value)).booleanValue();
0260: } catch (Throwable t) {
0261: ;
0262: }
0263: try {
0264: value = getServletConfig().getInitParameter("readonly");
0265: if (value != null)
0266: readOnly = (new Boolean(value)).booleanValue();
0267: } catch (Throwable t) {
0268: ;
0269: }
0270: try {
0271: value = getServletConfig().getInitParameter("output");
0272: output = Integer.parseInt(value);
0273: } catch (Throwable t) {
0274: ;
0275: }
0276:
0277: // Sanity check on the specified buffer sizes
0278: if (input < 256)
0279: input = 256;
0280: if (output < 256)
0281: output = 256;
0282:
0283: // Initialize the set of welcome files for this application
0284: welcomes = (String[]) getServletContext().getAttribute(
0285: Globals.WELCOME_FILES_ATTR);
0286:
0287: if (welcomes == null)
0288: welcomes = new String[0];
0289:
0290: if (debug > 0) {
0291: log("DefaultServlet.init: input buffer size=" + input
0292: + ", output buffer size=" + output);
0293: for (int i = 0; i < welcomes.length; i++) {
0294: log("DefaultServlet.init: welcome file=" + welcomes[i]);
0295: }
0296: }
0297:
0298: // Load the MD5 helper used to calculate signatures.
0299: try {
0300: md5Helper = MessageDigest.getInstance("MD5");
0301: } catch (NoSuchAlgorithmException e) {
0302: e.printStackTrace(System.out);
0303: throw new IllegalStateException();
0304: }
0305:
0306: log("DefaultServlet.init: done!");
0307: }
0308:
0309: // ------------------------------------------------------ Protected Methods
0310:
0311: /**
0312: * Get resources. This method will try to retrieve the resources through
0313: * JNDI first, then in the servlet context if JNDI has failed (it could be
0314: * disabled). It will return null.
0315: *
0316: * @return A JNDI DirContext, or null.
0317: */
0318: protected DirContext getResources() {
0319:
0320: DirContext result = null;
0321:
0322: // Try the servlet context
0323: try {
0324: result = (DirContext) getServletContext().getAttribute(
0325: Globals.RESOURCES_ATTR);
0326: } catch (ClassCastException e) {
0327: // Failed : Not the right type
0328: }
0329:
0330: if (result != null)
0331: return result;
0332:
0333: // Try JNDI
0334: try {
0335: result = (DirContext) new InitialContext()
0336: .lookup(RESOURCES_JNDI_NAME);
0337: } catch (NamingException e) {
0338: // Failed
0339: } catch (ClassCastException e) {
0340: // Failed : Not the right type
0341: }
0342:
0343: return result;
0344:
0345: }
0346:
0347: /**
0348: * Show HTTP header information.
0349: */
0350: protected void showRequestInfo(HttpServletRequest req) {
0351:
0352: System.out.println();
0353: System.out.println("SlideDAV Request Info");
0354: System.out.println();
0355:
0356: // Show generic info
0357: System.out.println("Encoding : " + req.getCharacterEncoding());
0358: System.out.println("Length : " + req.getContentLength());
0359: System.out.println("Type : " + req.getContentType());
0360:
0361: System.out.println();
0362: System.out.println("Parameters");
0363:
0364: Enumeration parameters = req.getParameterNames();
0365:
0366: while (parameters.hasMoreElements()) {
0367: String paramName = (String) parameters.nextElement();
0368: String[] values = req.getParameterValues(paramName);
0369: System.out.print(paramName + " : ");
0370: for (int i = 0; i < values.length; i++) {
0371: System.out.print(values[i] + ", ");
0372: }
0373: System.out.println();
0374: }
0375:
0376: System.out.println();
0377:
0378: System.out.println("Protocol : " + req.getProtocol());
0379: System.out.println("Address : " + req.getRemoteAddr());
0380: System.out.println("Host : " + req.getRemoteHost());
0381: System.out.println("Scheme : " + req.getScheme());
0382: System.out.println("Server Name : " + req.getServerName());
0383: System.out.println("Server Port : " + req.getServerPort());
0384:
0385: System.out.println();
0386: System.out.println("Attributes");
0387:
0388: Enumeration attributes = req.getAttributeNames();
0389:
0390: while (attributes.hasMoreElements()) {
0391: String attributeName = (String) attributes.nextElement();
0392: System.out.print(attributeName + " : ");
0393: System.out.println(req.getAttribute(attributeName)
0394: .toString());
0395: }
0396:
0397: System.out.println();
0398:
0399: // Show HTTP info
0400: System.out.println();
0401: System.out.println("HTTP Header Info");
0402: System.out.println();
0403:
0404: System.out
0405: .println("Authentication Type : " + req.getAuthType());
0406: System.out.println("HTTP Method : " + req.getMethod());
0407: System.out.println("Path Info : " + req.getPathInfo());
0408: System.out.println("Path translated : "
0409: + req.getPathTranslated());
0410: System.out.println("Query string : " + req.getQueryString());
0411: System.out.println("Remote user : " + req.getRemoteUser());
0412: System.out.println("Requested session id : "
0413: + req.getRequestedSessionId());
0414: System.out.println("Request URI : " + req.getRequestURI());
0415: System.out.println("Context path : " + req.getContextPath());
0416: System.out.println("Servlet path : " + req.getServletPath());
0417: System.out
0418: .println("User principal : " + req.getUserPrincipal());
0419:
0420: System.out.println();
0421: System.out.println("Headers : ");
0422:
0423: Enumeration headers = req.getHeaderNames();
0424:
0425: while (headers.hasMoreElements()) {
0426: String headerName = (String) headers.nextElement();
0427: System.out.print(headerName + " : ");
0428: System.out.println(req.getHeader(headerName));
0429: }
0430:
0431: System.out.println();
0432: System.out.println();
0433:
0434: }
0435:
0436: /**
0437: * Return the relative path associated with this servlet.
0438: *
0439: * @param request The servlet request we are processing
0440: */
0441: protected String getRelativePath(HttpServletRequest request) {
0442:
0443: // Are we being processed by a RequestDispatcher.include()?
0444: if (request.getAttribute("javax.servlet.include.request_uri") != null) {
0445: String result = (String) request
0446: .getAttribute("javax.servlet.include.path_info");
0447: if (result == null)
0448: result = (String) request
0449: .getAttribute("javax.servlet.include.servlet_path");
0450: if ((result == null) || (result.equals("")))
0451: result = "/";
0452: return (result);
0453: }
0454:
0455: // No, extract the desired path directly from the request
0456: String result = request.getPathInfo();
0457: if (result == null) {
0458: result = request.getServletPath();
0459: }
0460: if ((result == null) || (result.equals(""))) {
0461: result = "/";
0462: }
0463: return normalize(result);
0464:
0465: }
0466:
0467: /**
0468: * Process a GET request for the specified resource.
0469: *
0470: * @param request The servlet request we are processing
0471: * @param response The servlet response we are creating
0472: *
0473: * @exception IOException if an input/output error occurs
0474: * @exception ServletException if a servlet-specified error occurs
0475: */
0476: protected void doGet(HttpServletRequest request,
0477: HttpServletResponse response) throws IOException,
0478: ServletException {
0479:
0480: if (debug > 999)
0481: showRequestInfo(request);
0482:
0483: // Serve the requested resource, including the data content
0484: serveResource(request, response, true);
0485:
0486: }
0487:
0488: /**
0489: * Process a HEAD request for the specified resource.
0490: *
0491: * @param request The servlet request we are processing
0492: * @param response The servlet response we are creating
0493: *
0494: * @exception IOException if an input/output error occurs
0495: * @exception ServletException if a servlet-specified error occurs
0496: */
0497: protected void doHead(HttpServletRequest request,
0498: HttpServletResponse response) throws IOException,
0499: ServletException {
0500:
0501: // Serve the requested resource, without the data content
0502: serveResource(request, response, false);
0503:
0504: }
0505:
0506: /**
0507: * Process a POST request for the specified resource.
0508: *
0509: * @param request The servlet request we are processing
0510: * @param response The servlet response we are creating
0511: *
0512: * @exception IOException if an input/output error occurs
0513: * @exception ServletException if a servlet-specified error occurs
0514: */
0515: protected void doPost(HttpServletRequest request,
0516: HttpServletResponse response) throws IOException,
0517: ServletException {
0518: doGet(request, response);
0519: }
0520:
0521: /**
0522: * Process a POST request for the specified resource.
0523: *
0524: * @param request The servlet request we are processing
0525: * @param response The servlet response we are creating
0526: *
0527: * @exception IOException if an input/output error occurs
0528: * @exception ServletException if a servlet-specified error occurs
0529: */
0530: protected void doPut(HttpServletRequest req,
0531: HttpServletResponse resp) throws ServletException,
0532: IOException {
0533:
0534: if (readOnly) {
0535: resp.sendError(HttpServletResponse.SC_FORBIDDEN);
0536: return;
0537: }
0538:
0539: String path = getRelativePath(req);
0540:
0541: // Retrieve the resources
0542: DirContext resources = getResources();
0543:
0544: if (resources == null) {
0545: resp
0546: .sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
0547: return;
0548: }
0549:
0550: boolean exists = true;
0551: try {
0552: resources.lookup(path);
0553: } catch (NamingException e) {
0554: exists = false;
0555: }
0556:
0557: boolean result = true;
0558:
0559: // Temp. content file used to support partial PUT
0560: File contentFile = null;
0561:
0562: // Input stream for temp. content file used to support partial PUT
0563: FileInputStream contentFileInStream = null;
0564:
0565: ResourceInfo resourceInfo = new ResourceInfo(path, resources);
0566: Range range = parseContentRange(req, resp);
0567:
0568: InputStream resourceInputStream = null;
0569:
0570: // Append data specified in ranges to existing content for this
0571: // resource - create a temp. file on the local filesystem to
0572: // perform this operation
0573: // Assume just one range is specified for now
0574: if (range != null) {
0575: contentFile = executePartialPut(req, range, path);
0576: resourceInputStream = new FileInputStream(contentFile);
0577: } else {
0578: resourceInputStream = req.getInputStream();
0579: }
0580:
0581: try {
0582: Resource newResource = new Resource(resourceInputStream);
0583: // FIXME: Add attributes
0584: if (exists) {
0585: resources.rebind(path, newResource);
0586: } else {
0587: resources.bind(path, newResource);
0588: }
0589: } catch (NamingException e) {
0590: result = false;
0591: }
0592:
0593: if (result) {
0594: if (exists) {
0595: resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
0596: } else {
0597: resp.setStatus(HttpServletResponse.SC_CREATED);
0598: }
0599: } else {
0600: resp.sendError(HttpServletResponse.SC_CONFLICT);
0601: }
0602:
0603: }
0604:
0605: /**
0606: * Handle a partial PUT. New content specified in request is appended to
0607: * existing content in oldRevisionContent (if present). This code does
0608: * not support simultaneous partial updates to the same resource.
0609: */
0610: protected File executePartialPut(HttpServletRequest req,
0611: Range range, String path) throws IOException {
0612:
0613: // Append data specified in ranges to existing content for this
0614: // resource - create a temp. file on the local filesystem to
0615: // perform this operation
0616: File tempDir = (File) getServletContext().getAttribute(
0617: "javax.servlet.context.tempdir");
0618: // Convert all '/' characters to '.' in resourcePath
0619: String convertedResourcePath = path.replace('/', '.');
0620: File contentFile = new File(tempDir, convertedResourcePath);
0621: if (contentFile.createNewFile()) {
0622: // Clean up contentFile when Tomcat is terminated
0623: contentFile.deleteOnExit();
0624: }
0625:
0626: RandomAccessFile randAccessContentFile = new RandomAccessFile(
0627: contentFile, "rw");
0628:
0629: Resource oldResource = null;
0630: try {
0631: Object obj = getResources().lookup(path);
0632: if (obj instanceof Resource)
0633: oldResource = (Resource) obj;
0634: } catch (NamingException e) {
0635: }
0636:
0637: // Copy data in oldRevisionContent to contentFile
0638: if (oldResource != null) {
0639: BufferedInputStream bufOldRevStream = new BufferedInputStream(
0640: oldResource.streamContent(), BUFFER_SIZE);
0641:
0642: int numBytesRead;
0643: byte[] copyBuffer = new byte[BUFFER_SIZE];
0644: while ((numBytesRead = bufOldRevStream.read(copyBuffer)) != -1) {
0645: randAccessContentFile
0646: .write(copyBuffer, 0, numBytesRead);
0647: }
0648:
0649: bufOldRevStream.close();
0650: }
0651:
0652: randAccessContentFile.setLength(range.length);
0653:
0654: // Append data in request input stream to contentFile
0655: randAccessContentFile.seek(range.start);
0656: int numBytesRead;
0657: byte[] transferBuffer = new byte[BUFFER_SIZE];
0658: BufferedInputStream requestBufInStream = new BufferedInputStream(
0659: req.getInputStream(), BUFFER_SIZE);
0660: while ((numBytesRead = requestBufInStream.read(transferBuffer)) != -1) {
0661: randAccessContentFile
0662: .write(transferBuffer, 0, numBytesRead);
0663: }
0664: randAccessContentFile.close();
0665: requestBufInStream.close();
0666:
0667: return contentFile;
0668:
0669: }
0670:
0671: /**
0672: * Process a POST request for the specified resource.
0673: *
0674: * @param request The servlet request we are processing
0675: * @param response The servlet response we are creating
0676: *
0677: * @exception IOException if an input/output error occurs
0678: * @exception ServletException if a servlet-specified error occurs
0679: */
0680: protected void doDelete(HttpServletRequest req,
0681: HttpServletResponse resp) throws ServletException,
0682: IOException {
0683:
0684: if (readOnly) {
0685: resp.sendError(HttpServletResponse.SC_FORBIDDEN);
0686: return;
0687: }
0688:
0689: String path = getRelativePath(req);
0690:
0691: // Retrieve the Catalina context
0692: // Retrieve the resources
0693: DirContext resources = getResources();
0694:
0695: if (resources == null) {
0696: resp
0697: .sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
0698: return;
0699: }
0700:
0701: boolean exists = true;
0702: try {
0703: resources.lookup(path);
0704: } catch (NamingException e) {
0705: exists = false;
0706: }
0707:
0708: if (exists) {
0709: boolean result = true;
0710: try {
0711: resources.unbind(path);
0712: } catch (NamingException e) {
0713: result = false;
0714: }
0715: if (result) {
0716: resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
0717: } else {
0718: resp
0719: .sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
0720: }
0721: } else {
0722: resp.sendError(HttpServletResponse.SC_NOT_FOUND);
0723: }
0724:
0725: }
0726:
0727: /**
0728: * Check if the conditions specified in the optional If headers are
0729: * satisfied.
0730: *
0731: * @param request The servlet request we are processing
0732: * @param response The servlet response we are creating
0733: * @param resourceInfo File object
0734: * @return boolean true if the resource meets all the specified conditions,
0735: * and false if any of the conditions is not satisfied, in which case
0736: * request processing is stopped
0737: */
0738: protected boolean checkIfHeaders(HttpServletRequest request,
0739: HttpServletResponse response, ResourceInfo resourceInfo)
0740: throws IOException {
0741:
0742: return checkIfMatch(request, response, resourceInfo)
0743: && checkIfModifiedSince(request, response, resourceInfo)
0744: && checkIfNoneMatch(request, response, resourceInfo)
0745: && checkIfUnmodifiedSince(request, response,
0746: resourceInfo);
0747:
0748: }
0749:
0750: /**
0751: * Get the ETag associated with a file.
0752: *
0753: * @param resourceInfo File object
0754: * @param strong True if we want a strong ETag, in which case a checksum
0755: * of the file has to be calculated
0756: */
0757: protected String getETag(ResourceInfo resourceInfo) {
0758: if (resourceInfo.strongETag != null) {
0759: return resourceInfo.strongETag;
0760: } else if (resourceInfo.weakETag != null) {
0761: return resourceInfo.weakETag;
0762: } else {
0763: return "W/\"" + resourceInfo.length + "-"
0764: + resourceInfo.date + "\"";
0765: }
0766: }
0767:
0768: /**
0769: * Return a context-relative path, beginning with a "/", that represents
0770: * the canonical version of the specified path after ".." and "." elements
0771: * are resolved out. If the specified path attempts to go outside the
0772: * boundaries of the current context (i.e. too many ".." path elements
0773: * are present), return <code>null</code> instead.
0774: *
0775: * @param path Path to be normalized
0776: */
0777: protected String normalize(String path) {
0778:
0779: if (path == null)
0780: return null;
0781:
0782: // Create a place for the normalized path
0783: String normalized = path;
0784:
0785: /*
0786: * Commented out -- already URL-decoded in StandardContextMapper
0787: * Decoding twice leaves the container vulnerable to %25 --> '%'
0788: * attacks.
0789: *
0790: * if (normalized.indexOf('%') >= 0)
0791: * normalized = RequestUtil.URLDecode(normalized, "UTF8");
0792: */
0793:
0794: if (normalized == null)
0795: return (null);
0796:
0797: if (normalized.equals("/."))
0798: return "/";
0799:
0800: // Normalize the slashes and add leading slash if necessary
0801: if (normalized.indexOf('\\') >= 0)
0802: normalized = normalized.replace('\\', '/');
0803: if (!normalized.startsWith("/"))
0804: normalized = "/" + normalized;
0805:
0806: // Resolve occurrences of "//" in the normalized path
0807: while (true) {
0808: int index = normalized.indexOf("//");
0809: if (index < 0)
0810: break;
0811: normalized = normalized.substring(0, index)
0812: + normalized.substring(index + 1);
0813: }
0814:
0815: // Resolve occurrences of "/./" in the normalized path
0816: while (true) {
0817: int index = normalized.indexOf("/./");
0818: if (index < 0)
0819: break;
0820: normalized = normalized.substring(0, index)
0821: + normalized.substring(index + 2);
0822: }
0823:
0824: // Resolve occurrences of "/../" in the normalized path
0825: while (true) {
0826: int index = normalized.indexOf("/../");
0827: if (index < 0)
0828: break;
0829: if (index == 0)
0830: return (null); // Trying to go outside our context
0831: int index2 = normalized.lastIndexOf('/', index - 1);
0832: normalized = normalized.substring(0, index2)
0833: + normalized.substring(index + 3);
0834: }
0835:
0836: // Return the normalized path that we have completed
0837: return (normalized);
0838:
0839: }
0840:
0841: /**
0842: * URL rewriter.
0843: *
0844: * @param path Path which has to be rewiten
0845: */
0846: protected String rewriteUrl(String path) {
0847: return urlEncoder.encode(path);
0848: }
0849:
0850: /**
0851: * Display the size of a file.
0852: */
0853: protected void displaySize(StringBuffer buf, int filesize) {
0854:
0855: int leftside = filesize / 1024;
0856: int rightside = (filesize % 1024) / 103; // makes 1 digit
0857: // To avoid 0.0 for non-zero file, we bump to 0.1
0858: if (leftside == 0 && rightside == 0 && filesize != 0)
0859: rightside = 1;
0860: buf.append(leftside).append(".").append(rightside);
0861: buf.append(" KB");
0862:
0863: }
0864:
0865: /**
0866: * Serve the specified resource, optionally including the data content.
0867: *
0868: * @param request The servlet request we are processing
0869: * @param response The servlet response we are creating
0870: * @param content Should the content be included?
0871: *
0872: * @exception IOException if an input/output error occurs
0873: * @exception ServletException if a servlet-specified error occurs
0874: */
0875: protected void serveResource(HttpServletRequest request,
0876: HttpServletResponse response, boolean content)
0877: throws IOException, ServletException {
0878:
0879: // Identify the requested resource path
0880: String path = getRelativePath(request);
0881:
0882: log("1");
0883:
0884: if (debug > 0) {
0885: if (content)
0886: log("DefaultServlet.serveResource: Serving resource '"
0887: + path + "' headers and data");
0888: else
0889: log("DefaultServlet.serveResource: Serving resource '"
0890: + path + "' headers only");
0891:
0892: }
0893:
0894: log("2");
0895:
0896: // Retrieve the Catalina context and Resources implementation
0897: DirContext resources = getResources();
0898: ResourceInfo resourceInfo = new ResourceInfo(path, resources);
0899:
0900: log("3");
0901:
0902: if (!resourceInfo.exists) {
0903: log("3+");
0904: response.sendError(HttpServletResponse.SC_NOT_FOUND,
0905: request.getRequestURI());
0906: return;
0907: }
0908:
0909: log("4");
0910:
0911: // If the resource is not a collection, and the resource path
0912: // ends with "/" or "\", return NOT FOUND
0913: if (!resourceInfo.collection) {
0914: if (path.endsWith("/") || (path.endsWith("\\"))) {
0915: response.sendError(HttpServletResponse.SC_NOT_FOUND,
0916: request.getRequestURI());
0917: return;
0918: }
0919: }
0920:
0921: log("-");
0922:
0923: // If the resource is a collection (aka a directory), we check
0924: // the welcome files list.
0925: if (resourceInfo.collection) {
0926:
0927: if (!request.getRequestURI().endsWith("/")) {
0928: String redirectPath = path;
0929: String contextPath = request.getContextPath();
0930: if ((contextPath != null) && (!contextPath.equals("/"))) {
0931: redirectPath = contextPath + redirectPath;
0932: }
0933: if (!(redirectPath.endsWith("/")))
0934: redirectPath = redirectPath + "/";
0935: redirectPath = appendParameters(request, redirectPath);
0936: response.sendRedirect(redirectPath);
0937: return;
0938: }
0939:
0940: ResourceInfo welcomeFileInfo = checkWelcomeFiles(path,
0941: resources);
0942: if (welcomeFileInfo != null) {
0943: String redirectPath = welcomeFileInfo.path;
0944: String contextPath = request.getContextPath();
0945: if ((contextPath != null) && (!contextPath.equals("/"))) {
0946: redirectPath = contextPath + redirectPath;
0947: }
0948: redirectPath = appendParameters(request, redirectPath);
0949: response.sendRedirect(redirectPath);
0950: return;
0951: }
0952:
0953: } else {
0954:
0955: // Checking If headers
0956: boolean included = (request
0957: .getAttribute(Globals.CONTEXT_PATH_ATTR) != null);
0958: if (!included
0959: && !checkIfHeaders(request, response, resourceInfo)) {
0960: return;
0961: }
0962:
0963: }
0964:
0965: // Find content type.
0966: String contentType = getServletContext().getMimeType(
0967: resourceInfo.path);
0968:
0969: Vector ranges = null;
0970:
0971: if (resourceInfo.collection) {
0972: // Skip directory listings if we have been configured to
0973: // suppress them
0974: if (!listings) {
0975: response.sendError(HttpServletResponse.SC_NOT_FOUND,
0976: request.getRequestURI());
0977: return;
0978: }
0979: contentType = "text/html;charset=UTF-8";
0980:
0981: } else {
0982:
0983: // Parse range specifier
0984:
0985: ranges = parseRange(request, response, resourceInfo);
0986:
0987: // ETag header
0988: response.setHeader("ETag", getETag(resourceInfo));
0989:
0990: // Last-Modified header
0991: if (debug > 0)
0992: log("DefaultServlet.serveFile: lastModified='"
0993: + (new Timestamp(resourceInfo.date)).toString()
0994: + "'");
0995: response.setHeader("Last-Modified", resourceInfo.httpDate);
0996:
0997: }
0998:
0999: ServletOutputStream ostream = null;
1000: PrintWriter writer = null;
1001:
1002: if (content) {
1003:
1004: // Trying to retrieve the servlet output stream
1005:
1006: try {
1007: ostream = response.getOutputStream();
1008: } catch (IllegalStateException e) {
1009: // If it fails, we try to get a Writer instead if we're
1010: // trying to serve a text file
1011: if ((contentType != null)
1012: && (contentType.startsWith("text"))) {
1013: writer = response.getWriter();
1014: } else {
1015: throw e;
1016: }
1017: }
1018:
1019: }
1020:
1021: if ((resourceInfo.collection)
1022: || (((ranges == null) || (ranges.isEmpty())) && (request
1023: .getHeader("Range") == null))) {
1024:
1025: // Set the appropriate output headers
1026: if (contentType != null) {
1027: if (debug > 0)
1028: log("DefaultServlet.serveFile: contentType='"
1029: + contentType + "'");
1030: response.setContentType(contentType);
1031: }
1032: long contentLength = resourceInfo.length;
1033: if ((!resourceInfo.collection) && (contentLength >= 0)) {
1034: if (debug > 0)
1035: log("DefaultServlet.serveFile: contentLength="
1036: + contentLength);
1037: response.setContentLength((int) contentLength);
1038: }
1039:
1040: if (resourceInfo.collection) {
1041:
1042: if (content) {
1043: // Serve the directory browser
1044: resourceInfo.setStream(render(request
1045: .getContextPath(), resourceInfo));
1046: }
1047:
1048: }
1049:
1050: // Copy the input stream to our output stream (if requested)
1051: if (content) {
1052: try {
1053: response.setBufferSize(output);
1054: } catch (IllegalStateException e) {
1055: // Silent catch
1056: }
1057: if (ostream != null) {
1058: copy(resourceInfo, ostream);
1059: } else {
1060: copy(resourceInfo, writer);
1061: }
1062: }
1063:
1064: } else {
1065:
1066: if ((ranges == null) || (ranges.isEmpty()))
1067: return;
1068:
1069: // Partial content response.
1070:
1071: response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
1072:
1073: if (ranges.size() == 1) {
1074:
1075: Range range = (Range) ranges.elementAt(0);
1076: response.addHeader("Content-Range", "bytes "
1077: + range.start + "-" + range.end + "/"
1078: + range.length);
1079: response.setContentLength((int) (range.end
1080: - range.start + 1));
1081:
1082: if (contentType != null) {
1083: if (debug > 0)
1084: log("DefaultServlet.serveFile: contentType='"
1085: + contentType + "'");
1086: response.setContentType(contentType);
1087: }
1088:
1089: if (content) {
1090: try {
1091: response.setBufferSize(output);
1092: } catch (IllegalStateException e) {
1093: // Silent catch
1094: }
1095: if (ostream != null) {
1096: copy(resourceInfo, ostream, range);
1097: } else {
1098: copy(resourceInfo, writer, range);
1099: }
1100: }
1101:
1102: } else {
1103:
1104: response
1105: .setContentType("multipart/byteranges; boundary="
1106: + mimeSeparation);
1107:
1108: if (content) {
1109: try {
1110: response.setBufferSize(output);
1111: } catch (IllegalStateException e) {
1112: // Silent catch
1113: }
1114: if (ostream != null) {
1115: copy(resourceInfo, ostream, ranges.elements(),
1116: contentType);
1117: } else {
1118: copy(resourceInfo, writer, ranges.elements(),
1119: contentType);
1120: }
1121: }
1122:
1123: }
1124:
1125: }
1126:
1127: }
1128:
1129: /**
1130: * Parse the content-range header.
1131: *
1132: * @param request The servlet request we are processing
1133: * @param response The servlet response we are creating
1134: * @return Range
1135: */
1136: protected Range parseContentRange(HttpServletRequest request,
1137: HttpServletResponse response) throws IOException {
1138:
1139: // Retrieving the content-range header (if any is specified
1140: String rangeHeader = request.getHeader("Content-Range");
1141:
1142: if (rangeHeader == null)
1143: return null;
1144:
1145: // bytes is the only range unit supported
1146: if (!rangeHeader.startsWith("bytes")) {
1147: response.sendError(HttpServletResponse.SC_BAD_REQUEST);
1148: return null;
1149: }
1150:
1151: rangeHeader = rangeHeader.substring(6).trim();
1152:
1153: int dashPos = rangeHeader.indexOf('-');
1154: int slashPos = rangeHeader.indexOf('/');
1155:
1156: if (dashPos == -1) {
1157: response.sendError(HttpServletResponse.SC_BAD_REQUEST);
1158: return null;
1159: }
1160:
1161: if (slashPos == -1) {
1162: response.sendError(HttpServletResponse.SC_BAD_REQUEST);
1163: return null;
1164: }
1165:
1166: Range range = new Range();
1167:
1168: try {
1169: range.start = Long.parseLong(rangeHeader.substring(0,
1170: dashPos));
1171: range.end = Long.parseLong(rangeHeader.substring(
1172: dashPos + 1, slashPos));
1173: range.length = Long.parseLong(rangeHeader.substring(
1174: slashPos + 1, rangeHeader.length()));
1175: } catch (NumberFormatException e) {
1176: response.sendError(HttpServletResponse.SC_BAD_REQUEST);
1177: return null;
1178: }
1179:
1180: if (!range.validate()) {
1181: response.sendError(HttpServletResponse.SC_BAD_REQUEST);
1182: return null;
1183: }
1184:
1185: return range;
1186:
1187: }
1188:
1189: /**
1190: * Parse the range header.
1191: *
1192: * @param request The servlet request we are processing
1193: * @param response The servlet response we are creating
1194: * @return Vector of ranges
1195: */
1196: protected Vector parseRange(HttpServletRequest request,
1197: HttpServletResponse response, ResourceInfo resourceInfo)
1198: throws IOException {
1199:
1200: // Checking If-Range
1201: String headerValue = request.getHeader("If-Range");
1202: if (headerValue != null) {
1203:
1204: String eTag = getETag(resourceInfo);
1205: long lastModified = resourceInfo.date;
1206:
1207: Date date = null;
1208:
1209: // Parsing the HTTP Date
1210: for (int i = 0; (date == null) && (i < formats.length); i++) {
1211: try {
1212: date = formats[i].parse(headerValue);
1213: } catch (ParseException e) {
1214: ;
1215: }
1216: }
1217:
1218: if (date == null) {
1219:
1220: // If the ETag the client gave does not match the entity
1221: // etag, then the entire entity is returned.
1222: if (!eTag.equals(headerValue.trim()))
1223: return null;
1224:
1225: } else {
1226:
1227: // If the timestamp of the entity the client got is older than
1228: // the last modification date of the entity, the entire entity
1229: // is returned.
1230: if (lastModified > (date.getTime() + 1000))
1231: return null;
1232:
1233: }
1234:
1235: }
1236:
1237: long fileLength = resourceInfo.length;
1238:
1239: if (fileLength == 0)
1240: return null;
1241:
1242: // Retrieving the range header (if any is specified
1243: String rangeHeader = request.getHeader("Range");
1244:
1245: if (rangeHeader == null)
1246: return null;
1247: // bytes is the only range unit supported (and I don't see the point
1248: // of adding new ones).
1249: if (!rangeHeader.startsWith("bytes")) {
1250: response
1251: .addHeader("Content-Range", "bytes */" + fileLength);
1252: response
1253: .sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
1254: return null;
1255: }
1256:
1257: rangeHeader = rangeHeader.substring(6);
1258:
1259: // Vector which will contain all the ranges which are successfully
1260: // parsed.
1261: Vector result = new Vector();
1262: StringTokenizer commaTokenizer = new StringTokenizer(
1263: rangeHeader, ",");
1264:
1265: // Parsing the range list
1266: while (commaTokenizer.hasMoreTokens()) {
1267: String rangeDefinition = commaTokenizer.nextToken();
1268:
1269: Range currentRange = new Range();
1270: currentRange.length = fileLength;
1271:
1272: int dashPos = rangeDefinition.indexOf('-');
1273:
1274: if (dashPos == -1) {
1275: response.addHeader("Content-Range", "bytes */"
1276: + fileLength);
1277: response
1278: .sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
1279: return null;
1280: }
1281:
1282: if (dashPos == 0) {
1283:
1284: try {
1285: long offset = Long.parseLong(rangeDefinition);
1286: currentRange.start = fileLength + offset;
1287: currentRange.end = fileLength - 1;
1288: } catch (NumberFormatException e) {
1289: response.addHeader("Content-Range", "bytes */"
1290: + fileLength);
1291: response
1292: .sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
1293: return null;
1294: }
1295:
1296: } else {
1297:
1298: try {
1299: currentRange.start = Long.parseLong(rangeDefinition
1300: .substring(0, dashPos));
1301: if (dashPos < rangeDefinition.length() - 1)
1302: currentRange.end = Long
1303: .parseLong(rangeDefinition.substring(
1304: dashPos + 1, rangeDefinition
1305: .length()));
1306: else
1307: currentRange.end = fileLength - 1;
1308: } catch (NumberFormatException e) {
1309: response.addHeader("Content-Range", "bytes */"
1310: + fileLength);
1311: response
1312: .sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
1313: return null;
1314: }
1315:
1316: }
1317:
1318: if (!currentRange.validate()) {
1319: response.addHeader("Content-Range", "bytes */"
1320: + fileLength);
1321: response
1322: .sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
1323: return null;
1324: }
1325:
1326: result.addElement(currentRange);
1327: }
1328:
1329: return result;
1330: }
1331:
1332: /**
1333: * Append the request parameters to the redirection string before calling
1334: * sendRedirect.
1335: */
1336: protected String appendParameters(HttpServletRequest request,
1337: String redirectPath) {
1338:
1339: StringBuffer result = new StringBuffer(rewriteUrl(redirectPath));
1340:
1341: String query = request.getQueryString();
1342: if (query != null)
1343: result.append("?").append(query);
1344:
1345: return result.toString();
1346:
1347: }
1348:
1349: /**
1350: * Return an InputStream to an HTML representation of the contents
1351: * of this directory.
1352: *
1353: * @param contextPath Context path to which our internal paths are
1354: * relative
1355: */
1356: protected InputStream render
1357: (String contextPath, ResourceInfo resourceInfo) {
1358:
1359: String name = resourceInfo.path;
1360:
1361: // Number of characters to trim from the beginnings of filenames
1362: int trim = name.length();
1363: if (!name.endsWith("/"))
1364: trim += 1;
1365: if (name.equals("/"))
1366: trim = 1;
1367:
1368: // Prepare a writer to a buffered area
1369: ByteArrayOutputStream stream = new ByteArrayOutputStream();
1370: OutputStreamWriter osWriter = null;
1371: try {
1372: osWriter = new OutputStreamWriter(stream, "UTF8");
1373: } catch (Exception e) {
1374: // Should never happen
1375: osWriter = new OutputStreamWriter(stream);
1376: }
1377: PrintWriter writer = new PrintWriter(osWriter);
1378:
1379: StringBuffer sb = new StringBuffer();
1380:
1381: // Render the page header
1382: sb.append("<html>\r\n");
1383: sb.append("<head>\r\n");
1384: sb.append("<title>");
1385: sb.append(sm.getString("directory.title", name));
1386: sb.append("</title>\r\n");
1387: sb.append("<STYLE><!--");
1388: sb.append("H1{font-family : sans-serif,Arial,Tahoma;color : white;background-color : #0086b2;} ");
1389: sb.append("H3{font-family : sans-serif,Arial,Tahoma;color : white;background-color : #0086b2;} ");
1390: sb.append("BODY{font-family : sans-serif,Arial,Tahoma;color : black;background-color : white;} ");
1391: sb.append("B{color : white;background-color : #0086b2;} ");
1392: sb.append("A{color : black;} ");
1393: sb.append("HR{color : #0086b2;} ");
1394: sb.append("--></STYLE> ");
1395: sb.append("</head>\r\n");
1396: sb.append("<body>");
1397: sb.append("<h1>");
1398: sb.append(sm.getString("directory.title", name));
1399:
1400: // Render the link to our parent (if required)
1401: String parentDirectory = name;
1402: if (parentDirectory.endsWith("/")) {
1403: parentDirectory =
1404: parentDirectory.substring(0, parentDirectory.length() - 1);
1405: }
1406: int slash = parentDirectory.lastIndexOf('/');
1407: if (slash >= 0) {
1408: String parent = name.substring(0, slash);
1409: sb.append(" - <a href=\"");
1410: sb.append(rewriteUrl(contextPath));
1411: if (parent.equals(""))
1412: parent = "/";
1413: sb.append(rewriteUrl(parent));
1414: if (!parent.endsWith("/"))
1415: sb.append("/");
1416: sb.append("\">");
1417: sb.append("<b>");
1418: sb.append(sm.getString("directory.parent", parent));
1419: sb.append("</b>");
1420: sb.append("</a>");
1421: }
1422:
1423: sb.append("</h1>");
1424: sb.append("<HR size=\"1\" noshade>");
1425:
1426: sb.append("<table width=\"100%\" cellspacing=\"0\"" +
1427: " cellpadding=\"5\" align=\"center\">\r\n");
1428:
1429: // Render the column headings
1430: sb.append("<tr>\r\n");
1431: sb.append("<td align=\"left\"><font size=\"+1\"><strong>");
1432: sb.append(sm.getString("directory.filename"));
1433: sb.append("</strong></font></td>\r\n");
1434: sb.append("<td align=\"center\"><font size=\"+1\"><strong>");
1435: sb.append(sm.getString("directory.size"));
1436: sb.append("</strong></font></td>\r\n");
1437: sb.append("<td align=\"right\"><font size=\"+1\"><strong>");
1438: sb.append(sm.getString("directory.lastModified"));
1439: sb.append("</strong></font></td>\r\n");
1440: sb.append("</tr>");
1441:
1442: try {
1443:
1444: // Render the directory entries within this directory
1445: DirContext directory = resourceInfo.directory;
1446: NamingEnumeration enum =
1447: resourceInfo.resources.list(resourceInfo.path);
1448: boolean shade = false;
1449: while (enum.hasMoreElements()) {
1450:
1451: NameClassPair ncPair = (NameClassPair) enum.nextElement();
1452: String resourceName = ncPair.getName();
1453: ResourceInfo childResourceInfo =
1454: new ResourceInfo(resourceName, directory);
1455:
1456: String trimmed = resourceName/*.substring(trim)*/;
1457: if (trimmed.equalsIgnoreCase("WEB-INF") ||
1458: trimmed.equalsIgnoreCase("META-INF"))
1459: continue;
1460:
1461: sb.append("<tr");
1462: if (shade)
1463: sb.append(" bgcolor=\"eeeeee\"");
1464: sb.append(">\r\n");
1465: shade = !shade;
1466:
1467: sb.append("<td align=\"left\"> \r\n");
1468: sb.append("<a href=\"");
1469: sb.append(rewriteUrl(contextPath));
1470: resourceName = rewriteUrl(name + resourceName);
1471: sb.append(resourceName);
1472: if (childResourceInfo.collection)
1473: sb.append("/");
1474: sb.append("\"><tt>");
1475: sb.append(trimmed);
1476: if (childResourceInfo.collection)
1477: sb.append("/");
1478: sb.append("</tt></a></td>\r\n");
1479:
1480: sb.append("<td align=\"right\"><tt>");
1481: if (childResourceInfo.collection)
1482: sb.append(" ");
1483: else
1484: sb.append(renderSize(childResourceInfo.length));
1485: sb.append("</tt></td>\r\n");
1486:
1487: sb.append("<td align=\"right\"><tt>");
1488: sb.append(childResourceInfo.httpDate);
1489: sb.append("</tt></td>\r\n");
1490:
1491: sb.append("</tr>\r\n");
1492: }
1493:
1494: } catch (NamingException e) {
1495: // Something went wrong
1496: e.printStackTrace();
1497: }
1498:
1499: // Render the page footer
1500: sb.append("</table>\r\n");
1501:
1502: sb.append("<HR size=\"1\" noshade>");
1503: sb.append("<h3>").append(ServerInfo.getServerInfo()).append("</h3>");
1504: sb.append("</body>\r\n");
1505: sb.append("</html>\r\n");
1506:
1507: // Return an input stream to the underlying bytes
1508: writer.write(sb.toString());
1509: writer.flush();
1510: return (new ByteArrayInputStream(stream.toByteArray()));
1511:
1512: }
1513:
1514: /**
1515: * Render the specified file size (in bytes).
1516: *
1517: * @param size File size (in bytes)
1518: */
1519: protected String renderSize(long size) {
1520:
1521: long leftSide = size / 1024;
1522: long rightSide = (size % 1024) / 103; // Makes 1 digit
1523: if ((leftSide == 0) && (rightSide == 0) && (size > 0))
1524: rightSide = 1;
1525:
1526: return ("" + leftSide + "." + rightSide + " kb");
1527:
1528: }
1529:
1530: // -------------------------------------------------------- Private Methods
1531:
1532: /**
1533: * Check if the if-match condition is satisfied.
1534: *
1535: * @param request The servlet request we are processing
1536: * @param response The servlet response we are creating
1537: * @param resourceInfo File object
1538: * @return boolean true if the resource meets the specified condition,
1539: * and false if the condition is not satisfied, in which case request
1540: * processing is stopped
1541: */
1542: private boolean checkIfMatch(HttpServletRequest request,
1543: HttpServletResponse response, ResourceInfo resourceInfo)
1544: throws IOException {
1545:
1546: String eTag = getETag(resourceInfo);
1547: String headerValue = request.getHeader("If-Match");
1548: if (headerValue != null) {
1549: if (headerValue.indexOf('*') == -1) {
1550:
1551: StringTokenizer commaTokenizer = new StringTokenizer(
1552: headerValue, ",");
1553: boolean conditionSatisfied = false;
1554:
1555: while (!conditionSatisfied
1556: && commaTokenizer.hasMoreTokens()) {
1557: String currentToken = commaTokenizer.nextToken();
1558: if (currentToken.trim().equals(eTag))
1559: conditionSatisfied = true;
1560: }
1561:
1562: // If none of the given ETags match, 412 Precodition failed is
1563: // sent back
1564: if (!conditionSatisfied) {
1565: response
1566: .sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
1567: return false;
1568: }
1569:
1570: }
1571: }
1572: return true;
1573:
1574: }
1575:
1576: /**
1577: * Check if the if-modified-since condition is satisfied.
1578: *
1579: * @param request The servlet request we are processing
1580: * @param response The servlet response we are creating
1581: * @param resourceInfo File object
1582: * @return boolean true if the resource meets the specified condition,
1583: * and false if the condition is not satisfied, in which case request
1584: * processing is stopped
1585: */
1586: private boolean checkIfModifiedSince(HttpServletRequest request,
1587: HttpServletResponse response, ResourceInfo resourceInfo)
1588: throws IOException {
1589: try {
1590: long headerValue = request
1591: .getDateHeader("If-Modified-Since");
1592: long lastModified = resourceInfo.date;
1593: if (headerValue != -1) {
1594:
1595: // If an If-None-Match header has been specified, if modified since
1596: // is ignored.
1597: if ((request.getHeader("If-None-Match") == null)
1598: && (lastModified <= headerValue + 1000)) {
1599: // The entity has not been modified since the date
1600: // specified by the client. This is not an error case.
1601: response
1602: .setStatus(HttpServletResponse.SC_NOT_MODIFIED);
1603: return false;
1604: }
1605: }
1606: } catch (IllegalArgumentException illegalArgument) {
1607: return false;
1608: }
1609: return true;
1610:
1611: }
1612:
1613: /**
1614: * Check if the if-none-match condition is satisfied.
1615: *
1616: * @param request The servlet request we are processing
1617: * @param response The servlet response we are creating
1618: * @param resourceInfo File object
1619: * @return boolean true if the resource meets the specified condition,
1620: * and false if the condition is not satisfied, in which case request
1621: * processing is stopped
1622: */
1623: private boolean checkIfNoneMatch(HttpServletRequest request,
1624: HttpServletResponse response, ResourceInfo resourceInfo)
1625: throws IOException {
1626:
1627: String eTag = getETag(resourceInfo);
1628: String headerValue = request.getHeader("If-None-Match");
1629: if (headerValue != null) {
1630:
1631: boolean conditionSatisfied = false;
1632:
1633: if (!headerValue.equals("*")) {
1634:
1635: StringTokenizer commaTokenizer = new StringTokenizer(
1636: headerValue, ",");
1637:
1638: while (!conditionSatisfied
1639: && commaTokenizer.hasMoreTokens()) {
1640: String currentToken = commaTokenizer.nextToken();
1641: if (currentToken.trim().equals(eTag))
1642: conditionSatisfied = true;
1643: }
1644:
1645: } else {
1646: conditionSatisfied = true;
1647: }
1648:
1649: if (conditionSatisfied) {
1650:
1651: // For GET and HEAD, we should respond with
1652: // 304 Not Modified.
1653: // For every other method, 412 Precondition Failed is sent
1654: // back.
1655: if (("GET".equals(request.getMethod()))
1656: || ("HEAD".equals(request.getMethod()))) {
1657: response
1658: .setStatus(HttpServletResponse.SC_NOT_MODIFIED);
1659: return false;
1660: } else {
1661: response
1662: .sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
1663: return false;
1664: }
1665: }
1666: }
1667: return true;
1668:
1669: }
1670:
1671: /**
1672: * Check if the if-unmodified-since condition is satisfied.
1673: *
1674: * @param request The servlet request we are processing
1675: * @param response The servlet response we are creating
1676: * @param resourceInfo File object
1677: * @return boolean true if the resource meets the specified condition,
1678: * and false if the condition is not satisfied, in which case request
1679: * processing is stopped
1680: */
1681: private boolean checkIfUnmodifiedSince(HttpServletRequest request,
1682: HttpServletResponse response, ResourceInfo resourceInfo)
1683: throws IOException {
1684: try {
1685: long lastModified = resourceInfo.date;
1686: long headerValue = request
1687: .getDateHeader("If-Unmodified-Since");
1688: if (headerValue != -1) {
1689: if (lastModified > headerValue) {
1690: // The entity has not been modified since the date
1691: // specified by the client. This is not an error case.
1692: response
1693: .sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
1694: return false;
1695: }
1696: }
1697: } catch (IllegalArgumentException illegalArgument) {
1698: return false;
1699: }
1700: return true;
1701:
1702: }
1703:
1704: /**
1705: * Copy the contents of the specified input stream to the specified
1706: * output stream, and ensure that both streams are closed before returning
1707: * (even in the face of an exception).
1708: *
1709: * @param istream The input stream to read from
1710: * @param ostream The output stream to write to
1711: *
1712: * @exception IOException if an input/output error occurs
1713: */
1714: private void copy(ResourceInfo resourceInfo,
1715: ServletOutputStream ostream) throws IOException {
1716:
1717: IOException exception = null;
1718:
1719: // FIXME : i18n ?
1720: InputStream resourceInputStream = resourceInfo.getStream();
1721: InputStream istream = new BufferedInputStream(
1722: resourceInputStream, input);
1723:
1724: // Copy the input stream to the output stream
1725: exception = copyRange(istream, ostream);
1726:
1727: // Clean up the input stream
1728: try {
1729: istream.close();
1730: } catch (Throwable t) {
1731: ;
1732: }
1733:
1734: // Rethrow any exception that has occurred
1735: if (exception != null)
1736: throw exception;
1737:
1738: }
1739:
1740: /**
1741: * Copy the contents of the specified input stream to the specified
1742: * output stream, and ensure that both streams are closed before returning
1743: * (even in the face of an exception).
1744: *
1745: * @param istream The input stream to read from
1746: * @param writer The writer to write to
1747: *
1748: * @exception IOException if an input/output error occurs
1749: */
1750: private void copy(ResourceInfo resourceInfo, PrintWriter writer)
1751: throws IOException {
1752:
1753: IOException exception = null;
1754:
1755: InputStream resourceInputStream = resourceInfo.getStream();
1756: // FIXME : i18n ?
1757: Reader reader = new InputStreamReader(resourceInputStream);
1758:
1759: // Copy the input stream to the output stream
1760: exception = copyRange(reader, writer);
1761:
1762: // Clean up the reader
1763: try {
1764: reader.close();
1765: } catch (Throwable t) {
1766: ;
1767: }
1768:
1769: // Rethrow any exception that has occurred
1770: if (exception != null)
1771: throw exception;
1772:
1773: }
1774:
1775: /**
1776: * Copy the contents of the specified input stream to the specified
1777: * output stream, and ensure that both streams are closed before returning
1778: * (even in the face of an exception).
1779: *
1780: * @param resourceInfo The ResourceInfo object
1781: * @param ostream The output stream to write to
1782: * @param range Range the client wanted to retrieve
1783: * @exception IOException if an input/output error occurs
1784: */
1785: private void copy(ResourceInfo resourceInfo,
1786: ServletOutputStream ostream, Range range)
1787: throws IOException {
1788:
1789: IOException exception = null;
1790:
1791: InputStream resourceInputStream = resourceInfo.getStream();
1792: InputStream istream = new BufferedInputStream(
1793: resourceInputStream, input);
1794: exception = copyRange(istream, ostream, range.start, range.end);
1795:
1796: // Clean up the input stream
1797: try {
1798: istream.close();
1799: } catch (Throwable t) {
1800: ;
1801: }
1802:
1803: // Rethrow any exception that has occurred
1804: if (exception != null)
1805: throw exception;
1806:
1807: }
1808:
1809: /**
1810: * Copy the contents of the specified input stream to the specified
1811: * output stream, and ensure that both streams are closed before returning
1812: * (even in the face of an exception).
1813: *
1814: * @param resourceInfo The ResourceInfo object
1815: * @param writer The writer to write to
1816: * @param range Range the client wanted to retrieve
1817: * @exception IOException if an input/output error occurs
1818: */
1819: private void copy(ResourceInfo resourceInfo, PrintWriter writer,
1820: Range range) throws IOException {
1821:
1822: IOException exception = null;
1823:
1824: InputStream resourceInputStream = resourceInfo.getStream();
1825: Reader reader = new InputStreamReader(resourceInputStream);
1826: exception = copyRange(reader, writer, range.start, range.end);
1827:
1828: // Clean up the input stream
1829: try {
1830: reader.close();
1831: } catch (Throwable t) {
1832: ;
1833: }
1834:
1835: // Rethrow any exception that has occurred
1836: if (exception != null)
1837: throw exception;
1838:
1839: }
1840:
1841: /**
1842: * Copy the contents of the specified input stream to the specified
1843: * output stream, and ensure that both streams are closed before returning
1844: * (even in the face of an exception).
1845: *
1846: * @param resourceInfo The ResourceInfo object
1847: * @param ostream The output stream to write to
1848: * @param ranges Enumeration of the ranges the client wanted to retrieve
1849: * @param contentType Content type of the resource
1850: * @exception IOException if an input/output error occurs
1851: */
1852: private void copy(ResourceInfo resourceInfo,
1853: ServletOutputStream ostream, Enumeration ranges,
1854: String contentType) throws IOException {
1855:
1856: IOException exception = null;
1857:
1858: while ((exception == null) && (ranges.hasMoreElements())) {
1859:
1860: InputStream resourceInputStream = resourceInfo.getStream();
1861: InputStream istream = // FIXME: internationalization???????
1862: new BufferedInputStream(resourceInputStream, input);
1863:
1864: Range currentRange = (Range) ranges.nextElement();
1865:
1866: // Writing MIME header.
1867: ostream.println("--" + mimeSeparation);
1868: if (contentType != null)
1869: ostream.println("Content-Type: " + contentType);
1870: ostream.println("Content-Range: bytes "
1871: + currentRange.start + "-" + currentRange.end + "/"
1872: + currentRange.length);
1873: ostream.println();
1874:
1875: // Printing content
1876: exception = copyRange(istream, ostream, currentRange.start,
1877: currentRange.end);
1878:
1879: try {
1880: istream.close();
1881: } catch (Throwable t) {
1882: ;
1883: }
1884:
1885: }
1886:
1887: ostream.print("--" + mimeSeparation + "--");
1888:
1889: // Rethrow any exception that has occurred
1890: if (exception != null)
1891: throw exception;
1892:
1893: }
1894:
1895: /**
1896: * Copy the contents of the specified input stream to the specified
1897: * output stream, and ensure that both streams are closed before returning
1898: * (even in the face of an exception).
1899: *
1900: * @param resourceInfo The ResourceInfo object
1901: * @param writer The writer to write to
1902: * @param ranges Enumeration of the ranges the client wanted to retrieve
1903: * @param contentType Content type of the resource
1904: * @exception IOException if an input/output error occurs
1905: */
1906: private void copy(ResourceInfo resourceInfo, PrintWriter writer,
1907: Enumeration ranges, String contentType) throws IOException {
1908:
1909: IOException exception = null;
1910:
1911: while ((exception == null) && (ranges.hasMoreElements())) {
1912:
1913: InputStream resourceInputStream = resourceInfo.getStream();
1914: Reader reader = new InputStreamReader(resourceInputStream);
1915:
1916: Range currentRange = (Range) ranges.nextElement();
1917:
1918: // Writing MIME header.
1919: writer.println("--" + mimeSeparation);
1920: if (contentType != null)
1921: writer.println("Content-Type: " + contentType);
1922: writer.println("Content-Range: bytes " + currentRange.start
1923: + "-" + currentRange.end + "/"
1924: + currentRange.length);
1925: writer.println();
1926:
1927: // Printing content
1928: exception = copyRange(reader, writer, currentRange.start,
1929: currentRange.end);
1930:
1931: try {
1932: reader.close();
1933: } catch (Throwable t) {
1934: ;
1935: }
1936:
1937: }
1938:
1939: writer.print("--" + mimeSeparation + "--");
1940:
1941: // Rethrow any exception that has occurred
1942: if (exception != null)
1943: throw exception;
1944:
1945: }
1946:
1947: /**
1948: * Copy the contents of the specified input stream to the specified
1949: * output stream, and ensure that both streams are closed before returning
1950: * (even in the face of an exception).
1951: *
1952: * @param istream The input stream to read from
1953: * @param ostream The output stream to write to
1954: * @return Exception which occurred during processing
1955: */
1956: private IOException copyRange(InputStream istream,
1957: ServletOutputStream ostream) {
1958:
1959: // Copy the input stream to the output stream
1960: IOException exception = null;
1961: byte buffer[] = new byte[input];
1962: int len = buffer.length;
1963: while (true) {
1964: try {
1965: len = istream.read(buffer);
1966: if (len == -1)
1967: break;
1968: ostream.write(buffer, 0, len);
1969: } catch (IOException e) {
1970: exception = e;
1971: len = -1;
1972: break;
1973: }
1974: }
1975: return exception;
1976:
1977: }
1978:
1979: /**
1980: * Copy the contents of the specified input stream to the specified
1981: * output stream, and ensure that both streams are closed before returning
1982: * (even in the face of an exception).
1983: *
1984: * @param reader The reader to read from
1985: * @param writer The writer to write to
1986: * @return Exception which occurred during processing
1987: */
1988: private IOException copyRange(Reader reader, PrintWriter writer) {
1989:
1990: // Copy the input stream to the output stream
1991: IOException exception = null;
1992: char buffer[] = new char[input];
1993: int len = buffer.length;
1994: while (true) {
1995: try {
1996: len = reader.read(buffer);
1997: if (len == -1)
1998: break;
1999: writer.write(buffer, 0, len);
2000: } catch (IOException e) {
2001: exception = e;
2002: len = -1;
2003: break;
2004: }
2005: }
2006: return exception;
2007:
2008: }
2009:
2010: /**
2011: * Copy the contents of the specified input stream to the specified
2012: * output stream, and ensure that both streams are closed before returning
2013: * (even in the face of an exception).
2014: *
2015: * @param istream The input stream to read from
2016: * @param ostream The output stream to write to
2017: * @param start Start of the range which will be copied
2018: * @param end End of the range which will be copied
2019: * @return Exception which occurred during processing
2020: */
2021: private IOException copyRange(InputStream istream,
2022: ServletOutputStream ostream, long start, long end) {
2023:
2024: if (debug > 10)
2025: System.out.println("Serving bytes:" + start + "-" + end);
2026:
2027: try {
2028: istream.skip(start);
2029: } catch (IOException e) {
2030: return e;
2031: }
2032:
2033: IOException exception = null;
2034: long bytesToRead = end - start + 1;
2035:
2036: byte buffer[] = new byte[input];
2037: int len = buffer.length;
2038: while ((bytesToRead > 0) && (len >= buffer.length)) {
2039: try {
2040: len = istream.read(buffer);
2041: if (bytesToRead >= len) {
2042: ostream.write(buffer, 0, len);
2043: bytesToRead -= len;
2044: } else {
2045: ostream.write(buffer, 0, (int) bytesToRead);
2046: bytesToRead = 0;
2047: }
2048: } catch (IOException e) {
2049: exception = e;
2050: len = -1;
2051: }
2052: if (len < buffer.length)
2053: break;
2054: }
2055:
2056: return exception;
2057:
2058: }
2059:
2060: /**
2061: * Copy the contents of the specified input stream to the specified
2062: * output stream, and ensure that both streams are closed before returning
2063: * (even in the face of an exception).
2064: *
2065: * @param reader The reader to read from
2066: * @param writer The writer to write to
2067: * @param start Start of the range which will be copied
2068: * @param end End of the range which will be copied
2069: * @return Exception which occurred during processing
2070: */
2071: private IOException copyRange(Reader reader, PrintWriter writer,
2072: long start, long end) {
2073:
2074: try {
2075: reader.skip(start);
2076: } catch (IOException e) {
2077: return e;
2078: }
2079:
2080: IOException exception = null;
2081: long bytesToRead = end - start + 1;
2082:
2083: char buffer[] = new char[input];
2084: int len = buffer.length;
2085: while ((bytesToRead > 0) && (len >= buffer.length)) {
2086: try {
2087: len = reader.read(buffer);
2088: if (bytesToRead >= len) {
2089: writer.write(buffer, 0, len);
2090: bytesToRead -= len;
2091: } else {
2092: writer.write(buffer, 0, (int) bytesToRead);
2093: bytesToRead = 0;
2094: }
2095: } catch (IOException e) {
2096: exception = e;
2097: len = -1;
2098: }
2099: if (len < buffer.length)
2100: break;
2101: }
2102:
2103: return exception;
2104:
2105: }
2106:
2107: /**
2108: * Check to see if a default page exists.
2109: *
2110: * @param pathname Pathname of the file to be served
2111: */
2112: private ResourceInfo checkWelcomeFiles(String pathname,
2113: DirContext resources) {
2114:
2115: String collectionName = pathname;
2116: if (!pathname.endsWith("/")) {
2117: collectionName += "/";
2118: }
2119:
2120: // Refresh our currently defined set of welcome files
2121: synchronized (welcomes) {
2122: welcomes = (String[]) getServletContext().getAttribute(
2123: Globals.WELCOME_FILES_ATTR);
2124: if (welcomes == null)
2125: welcomes = new String[0];
2126: }
2127:
2128: // Serve a welcome resource or file if one exists
2129: for (int i = 0; i < welcomes.length; i++) {
2130:
2131: // Does the specified resource exist?
2132: String resourceName = collectionName + welcomes[i];
2133: ResourceInfo resourceInfo = new ResourceInfo(resourceName,
2134: resources);
2135: if (resourceInfo.exists()) {
2136: return resourceInfo;
2137: }
2138:
2139: }
2140:
2141: return null;
2142:
2143: }
2144:
2145: // ------------------------------------------------------ Range Inner Class
2146:
2147: private class Range {
2148:
2149: public long start;
2150: public long end;
2151: public long length;
2152:
2153: /**
2154: * Validate range.
2155: */
2156: public boolean validate() {
2157: if (end >= length)
2158: end = length - 1;
2159: return ((start >= 0) && (end >= 0) && (start <= end) && (length > 0));
2160: }
2161:
2162: public void recycle() {
2163: start = 0;
2164: end = 0;
2165: length = 0;
2166: }
2167:
2168: }
2169:
2170: // ---------------------------------------------- ResourceInfo Inner Class
2171:
2172: protected class ResourceInfo {
2173:
2174: /**
2175: * Constructor.
2176: *
2177: * @param pathname Path name of the file
2178: */
2179: public ResourceInfo(String path, DirContext resources) {
2180: set(path, resources);
2181: }
2182:
2183: public Object object;
2184: public DirContext directory;
2185: public Resource file;
2186: public Attributes attributes;
2187: public String path;
2188: public long creationDate;
2189: public String httpDate;
2190: public long date;
2191: public long length;
2192: public boolean collection;
2193: public String weakETag;
2194: public String strongETag;
2195: public boolean exists;
2196: public DirContext resources;
2197: protected InputStream is;
2198:
2199: public void recycle() {
2200: object = null;
2201: directory = null;
2202: file = null;
2203: attributes = null;
2204: path = null;
2205: creationDate = 0;
2206: httpDate = null;
2207: date = 0;
2208: length = -1;
2209: collection = true;
2210: weakETag = null;
2211: strongETag = null;
2212: exists = false;
2213: resources = null;
2214: is = null;
2215: }
2216:
2217: public void set(String path, DirContext resources) {
2218:
2219: recycle();
2220:
2221: this .path = path;
2222: this .resources = resources;
2223: exists = true;
2224: try {
2225: object = resources.lookup(path);
2226: if (object instanceof Resource) {
2227: file = (Resource) object;
2228: collection = false;
2229: } else if (object instanceof DirContext) {
2230: directory = (DirContext) object;
2231: collection = true;
2232: } else {
2233: // Don't know how to serve another object type
2234: exists = false;
2235: }
2236: } catch (NamingException e) {
2237: exists = false;
2238: }
2239: if (exists) {
2240: try {
2241: attributes = resources.getAttributes(path);
2242: if (attributes instanceof ResourceAttributes) {
2243: ResourceAttributes tempAttrs = (ResourceAttributes) attributes;
2244: Date tempDate = tempAttrs.getCreationDate();
2245: if (tempDate != null)
2246: creationDate = tempDate.getTime();
2247: tempDate = tempAttrs.getLastModifiedDate();
2248: if (tempDate != null) {
2249: httpDate = FastHttpDateFormat
2250: .getDate(tempDate);
2251: date = tempDate.getTime();
2252: } else {
2253: httpDate = FastHttpDateFormat
2254: .getCurrentDate();
2255: }
2256: weakETag = tempAttrs.getETag();
2257: strongETag = tempAttrs.getETag(true);
2258: length = tempAttrs.getContentLength();
2259: }
2260: } catch (NamingException e) {
2261: // Shouldn't happen, the implementation of the DirContext
2262: // is probably broken
2263: exists = false;
2264: }
2265: }
2266:
2267: }
2268:
2269: /**
2270: * Test if the associated resource exists.
2271: */
2272: public boolean exists() {
2273: return exists;
2274: }
2275:
2276: /**
2277: * String representation.
2278: */
2279: public String toString() {
2280: return path;
2281: }
2282:
2283: /**
2284: * Set IS.
2285: */
2286: public void setStream(InputStream is) {
2287: this .is = is;
2288: }
2289:
2290: /**
2291: * Get IS from resource.
2292: */
2293: public InputStream getStream() throws IOException {
2294: if (is != null)
2295: return is;
2296: if (file != null)
2297: return (file.streamContent());
2298: else
2299: return null;
2300: }
2301:
2302: }
2303:
2304: }
|