0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017:
0018: package org.apache.catalina.servlets;
0019:
0020: import java.io.BufferedInputStream;
0021: import java.io.ByteArrayInputStream;
0022: import java.io.ByteArrayOutputStream;
0023: import java.io.File;
0024: import java.io.FileInputStream;
0025: import java.io.IOException;
0026: import java.io.InputStream;
0027: import java.io.InputStreamReader;
0028: import java.io.OutputStreamWriter;
0029: import java.io.PrintWriter;
0030: import java.io.RandomAccessFile;
0031: import java.io.Reader;
0032: import java.io.StringReader;
0033: import java.io.StringWriter;
0034: import java.util.ArrayList;
0035: import java.util.Iterator;
0036: import java.util.StringTokenizer;
0037:
0038: import javax.naming.InitialContext;
0039: import javax.naming.NameClassPair;
0040: import javax.naming.NamingEnumeration;
0041: import javax.naming.NamingException;
0042: import javax.naming.directory.DirContext;
0043: import javax.servlet.ServletException;
0044: import javax.servlet.ServletOutputStream;
0045: import javax.servlet.UnavailableException;
0046: import javax.servlet.http.HttpServlet;
0047: import javax.servlet.http.HttpServletRequest;
0048: import javax.servlet.http.HttpServletResponse;
0049: import javax.xml.transform.Source;
0050: import javax.xml.transform.Transformer;
0051: import javax.xml.transform.TransformerException;
0052: import javax.xml.transform.TransformerFactory;
0053: import javax.xml.transform.stream.StreamResult;
0054: import javax.xml.transform.stream.StreamSource;
0055:
0056: import org.apache.catalina.Globals;
0057: import org.apache.catalina.util.RequestUtil;
0058: import org.apache.catalina.util.ServerInfo;
0059: import org.apache.catalina.util.StringManager;
0060: import org.apache.catalina.util.URLEncoder;
0061: import org.apache.naming.resources.CacheEntry;
0062: import org.apache.naming.resources.ProxyDirContext;
0063: import org.apache.naming.resources.Resource;
0064: import org.apache.naming.resources.ResourceAttributes;
0065:
0066: /**
0067: * The default resource-serving servlet for most web applications,
0068: * used to serve static resources such as HTML pages and images.
0069: *
0070: * @author Craig R. McClanahan
0071: * @author Remy Maucherat
0072: * @version $Revision: 543680 $ $Date: 2007-06-02 02:42:36 +0200 (sam., 02 juin 2007) $
0073: */
0074:
0075: public class DefaultServlet extends HttpServlet {
0076:
0077: // ----------------------------------------------------- Instance Variables
0078:
0079: /**
0080: * The debugging detail level for this servlet.
0081: */
0082: protected int debug = 0;
0083:
0084: /**
0085: * The input buffer size to use when serving resources.
0086: */
0087: protected int input = 2048;
0088:
0089: /**
0090: * Should we generate directory listings?
0091: */
0092: protected boolean listings = false;
0093:
0094: /**
0095: * Read only flag. By default, it's set to true.
0096: */
0097: protected boolean readOnly = true;
0098:
0099: /**
0100: * The output buffer size to use when serving resources.
0101: */
0102: protected int output = 2048;
0103:
0104: /**
0105: * Array containing the safe characters set.
0106: */
0107: protected static URLEncoder urlEncoder;
0108:
0109: /**
0110: * Allow customized directory listing per directory.
0111: */
0112: protected String localXsltFile = null;
0113:
0114: /**
0115: * Allow customized directory listing per instance.
0116: */
0117: protected String globalXsltFile = null;
0118:
0119: /**
0120: * Allow a readme file to be included.
0121: */
0122: protected String readmeFile = null;
0123:
0124: /**
0125: * Proxy directory context.
0126: */
0127: protected ProxyDirContext resources = null;
0128:
0129: /**
0130: * File encoding to be used when reading static files. If none is specified
0131: * the platform default is used.
0132: */
0133: protected String fileEncoding = null;
0134:
0135: /**
0136: * Minimum size for sendfile usage in bytes.
0137: */
0138: protected int sendfileSize = 48 * 1024;
0139:
0140: /**
0141: * Full range marker.
0142: */
0143: protected static ArrayList FULL = new ArrayList();
0144:
0145: // ----------------------------------------------------- Static Initializer
0146:
0147: /**
0148: * GMT timezone - all HTTP dates are on GMT
0149: */
0150: static {
0151: urlEncoder = new URLEncoder();
0152: urlEncoder.addSafeCharacter('-');
0153: urlEncoder.addSafeCharacter('_');
0154: urlEncoder.addSafeCharacter('.');
0155: urlEncoder.addSafeCharacter('*');
0156: urlEncoder.addSafeCharacter('/');
0157: }
0158:
0159: /**
0160: * MIME multipart separation string
0161: */
0162: protected static final String mimeSeparation = "CATALINA_MIME_BOUNDARY";
0163:
0164: /**
0165: * JNDI resources name.
0166: */
0167: protected static final String RESOURCES_JNDI_NAME = "java:/comp/Resources";
0168:
0169: /**
0170: * The string manager for this package.
0171: */
0172: protected static StringManager sm = StringManager
0173: .getManager(Constants.Package);
0174:
0175: /**
0176: * Size of file transfer buffer in bytes.
0177: */
0178: protected static final int BUFFER_SIZE = 4096;
0179:
0180: // --------------------------------------------------------- Public Methods
0181:
0182: /**
0183: * Finalize this servlet.
0184: */
0185: public void destroy() {
0186: }
0187:
0188: /**
0189: * Initialize this servlet.
0190: */
0191: public void init() throws ServletException {
0192:
0193: if (getServletConfig().getInitParameter("debug") != null)
0194: debug = Integer.parseInt(getServletConfig()
0195: .getInitParameter("debug"));
0196:
0197: if (getServletConfig().getInitParameter("input") != null)
0198: input = Integer.parseInt(getServletConfig()
0199: .getInitParameter("input"));
0200:
0201: if (getServletConfig().getInitParameter("output") != null)
0202: output = Integer.parseInt(getServletConfig()
0203: .getInitParameter("output"));
0204:
0205: listings = Boolean.parseBoolean(getServletConfig()
0206: .getInitParameter("listings"));
0207:
0208: if (getServletConfig().getInitParameter("readonly") != null)
0209: readOnly = Boolean.parseBoolean(getServletConfig()
0210: .getInitParameter("readonly"));
0211:
0212: if (getServletConfig().getInitParameter("sendfileSize") != null)
0213: sendfileSize = Integer.parseInt(getServletConfig()
0214: .getInitParameter("sendfileSize")) * 1024;
0215:
0216: fileEncoding = getServletConfig().getInitParameter(
0217: "fileEncoding");
0218:
0219: globalXsltFile = getServletConfig().getInitParameter(
0220: "globalXsltFile");
0221: localXsltFile = getServletConfig().getInitParameter(
0222: "localXsltFile");
0223: readmeFile = getServletConfig().getInitParameter("readmeFile");
0224:
0225: // Sanity check on the specified buffer sizes
0226: if (input < 256)
0227: input = 256;
0228: if (output < 256)
0229: output = 256;
0230:
0231: if (debug > 0) {
0232: log("DefaultServlet.init: input buffer size=" + input
0233: + ", output buffer size=" + output);
0234: }
0235:
0236: // Load the proxy dir context.
0237: resources = (ProxyDirContext) getServletContext().getAttribute(
0238: Globals.RESOURCES_ATTR);
0239: if (resources == null) {
0240: try {
0241: resources = (ProxyDirContext) new InitialContext()
0242: .lookup(RESOURCES_JNDI_NAME);
0243: } catch (NamingException e) {
0244: // Failed
0245: throw new ServletException("No resources", e);
0246: }
0247: }
0248:
0249: if (resources == null) {
0250: throw new UnavailableException("No resources");
0251: }
0252:
0253: }
0254:
0255: // ------------------------------------------------------ Protected Methods
0256:
0257: /**
0258: * Return the relative path associated with this servlet.
0259: *
0260: * @param request The servlet request we are processing
0261: */
0262: protected String getRelativePath(HttpServletRequest request) {
0263:
0264: // Are we being processed by a RequestDispatcher.include()?
0265: if (request.getAttribute(Globals.INCLUDE_REQUEST_URI_ATTR) != null) {
0266: String result = (String) request
0267: .getAttribute(Globals.INCLUDE_PATH_INFO_ATTR);
0268: if (result == null)
0269: result = (String) request
0270: .getAttribute(Globals.INCLUDE_SERVLET_PATH_ATTR);
0271: if ((result == null) || (result.equals("")))
0272: result = "/";
0273: return (result);
0274: }
0275:
0276: // No, extract the desired path directly from the request
0277: String result = request.getPathInfo();
0278: if (result == null) {
0279: result = request.getServletPath();
0280: }
0281: if ((result == null) || (result.equals(""))) {
0282: result = "/";
0283: }
0284: return (result);
0285:
0286: }
0287:
0288: /**
0289: * Process a GET request for the specified resource.
0290: *
0291: * @param request The servlet request we are processing
0292: * @param response The servlet response we are creating
0293: *
0294: * @exception IOException if an input/output error occurs
0295: * @exception ServletException if a servlet-specified error occurs
0296: */
0297: protected void doGet(HttpServletRequest request,
0298: HttpServletResponse response) throws IOException,
0299: ServletException {
0300:
0301: // Serve the requested resource, including the data content
0302: serveResource(request, response, true);
0303:
0304: }
0305:
0306: /**
0307: * Process a HEAD request for the specified resource.
0308: *
0309: * @param request The servlet request we are processing
0310: * @param response The servlet response we are creating
0311: *
0312: * @exception IOException if an input/output error occurs
0313: * @exception ServletException if a servlet-specified error occurs
0314: */
0315: protected void doHead(HttpServletRequest request,
0316: HttpServletResponse response) throws IOException,
0317: ServletException {
0318:
0319: // Serve the requested resource, without the data content
0320: serveResource(request, response, false);
0321:
0322: }
0323:
0324: /**
0325: * Process a POST request for the specified resource.
0326: *
0327: * @param request The servlet request we are processing
0328: * @param response The servlet response we are creating
0329: *
0330: * @exception IOException if an input/output error occurs
0331: * @exception ServletException if a servlet-specified error occurs
0332: */
0333: protected void doPost(HttpServletRequest request,
0334: HttpServletResponse response) throws IOException,
0335: ServletException {
0336: doGet(request, response);
0337: }
0338:
0339: /**
0340: * Process a POST request for the specified resource.
0341: *
0342: * @param req The servlet request we are processing
0343: * @param resp The servlet response we are creating
0344: *
0345: * @exception IOException if an input/output error occurs
0346: * @exception ServletException if a servlet-specified error occurs
0347: */
0348: protected void doPut(HttpServletRequest req,
0349: HttpServletResponse resp) throws ServletException,
0350: IOException {
0351:
0352: if (readOnly) {
0353: resp.sendError(HttpServletResponse.SC_FORBIDDEN);
0354: return;
0355: }
0356:
0357: String path = getRelativePath(req);
0358:
0359: boolean exists = true;
0360: try {
0361: resources.lookup(path);
0362: } catch (NamingException e) {
0363: exists = false;
0364: }
0365:
0366: boolean result = true;
0367:
0368: // Temp. content file used to support partial PUT
0369: File contentFile = null;
0370:
0371: Range range = parseContentRange(req, resp);
0372:
0373: InputStream resourceInputStream = null;
0374:
0375: // Append data specified in ranges to existing content for this
0376: // resource - create a temp. file on the local filesystem to
0377: // perform this operation
0378: // Assume just one range is specified for now
0379: if (range != null) {
0380: contentFile = executePartialPut(req, range, path);
0381: resourceInputStream = new FileInputStream(contentFile);
0382: } else {
0383: resourceInputStream = req.getInputStream();
0384: }
0385:
0386: try {
0387: Resource newResource = new Resource(resourceInputStream);
0388: // FIXME: Add attributes
0389: if (exists) {
0390: resources.rebind(path, newResource);
0391: } else {
0392: resources.bind(path, newResource);
0393: }
0394: } catch (NamingException e) {
0395: result = false;
0396: }
0397:
0398: if (result) {
0399: if (exists) {
0400: resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
0401: } else {
0402: resp.setStatus(HttpServletResponse.SC_CREATED);
0403: }
0404: } else {
0405: resp.sendError(HttpServletResponse.SC_CONFLICT);
0406: }
0407:
0408: }
0409:
0410: /**
0411: * Handle a partial PUT. New content specified in request is appended to
0412: * existing content in oldRevisionContent (if present). This code does
0413: * not support simultaneous partial updates to the same resource.
0414: */
0415: protected File executePartialPut(HttpServletRequest req,
0416: Range range, String path) throws IOException {
0417:
0418: // Append data specified in ranges to existing content for this
0419: // resource - create a temp. file on the local filesystem to
0420: // perform this operation
0421: File tempDir = (File) getServletContext().getAttribute(
0422: "javax.servlet.context.tempdir");
0423: // Convert all '/' characters to '.' in resourcePath
0424: String convertedResourcePath = path.replace('/', '.');
0425: File contentFile = new File(tempDir, convertedResourcePath);
0426: if (contentFile.createNewFile()) {
0427: // Clean up contentFile when Tomcat is terminated
0428: contentFile.deleteOnExit();
0429: }
0430:
0431: RandomAccessFile randAccessContentFile = new RandomAccessFile(
0432: contentFile, "rw");
0433:
0434: Resource oldResource = null;
0435: try {
0436: Object obj = resources.lookup(path);
0437: if (obj instanceof Resource)
0438: oldResource = (Resource) obj;
0439: } catch (NamingException e) {
0440: ;
0441: }
0442:
0443: // Copy data in oldRevisionContent to contentFile
0444: if (oldResource != null) {
0445: BufferedInputStream bufOldRevStream = new BufferedInputStream(
0446: oldResource.streamContent(), BUFFER_SIZE);
0447:
0448: int numBytesRead;
0449: byte[] copyBuffer = new byte[BUFFER_SIZE];
0450: while ((numBytesRead = bufOldRevStream.read(copyBuffer)) != -1) {
0451: randAccessContentFile
0452: .write(copyBuffer, 0, numBytesRead);
0453: }
0454:
0455: bufOldRevStream.close();
0456: }
0457:
0458: randAccessContentFile.setLength(range.length);
0459:
0460: // Append data in request input stream to contentFile
0461: randAccessContentFile.seek(range.start);
0462: int numBytesRead;
0463: byte[] transferBuffer = new byte[BUFFER_SIZE];
0464: BufferedInputStream requestBufInStream = new BufferedInputStream(
0465: req.getInputStream(), BUFFER_SIZE);
0466: while ((numBytesRead = requestBufInStream.read(transferBuffer)) != -1) {
0467: randAccessContentFile
0468: .write(transferBuffer, 0, numBytesRead);
0469: }
0470: randAccessContentFile.close();
0471: requestBufInStream.close();
0472:
0473: return contentFile;
0474:
0475: }
0476:
0477: /**
0478: * Process a POST request for the specified resource.
0479: *
0480: * @param req The servlet request we are processing
0481: * @param resp The servlet response we are creating
0482: *
0483: * @exception IOException if an input/output error occurs
0484: * @exception ServletException if a servlet-specified error occurs
0485: */
0486: protected void doDelete(HttpServletRequest req,
0487: HttpServletResponse resp) throws ServletException,
0488: IOException {
0489:
0490: if (readOnly) {
0491: resp.sendError(HttpServletResponse.SC_FORBIDDEN);
0492: return;
0493: }
0494:
0495: String path = getRelativePath(req);
0496:
0497: boolean exists = true;
0498: try {
0499: resources.lookup(path);
0500: } catch (NamingException e) {
0501: exists = false;
0502: }
0503:
0504: if (exists) {
0505: boolean result = true;
0506: try {
0507: resources.unbind(path);
0508: } catch (NamingException e) {
0509: result = false;
0510: }
0511: if (result) {
0512: resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
0513: } else {
0514: resp
0515: .sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
0516: }
0517: } else {
0518: resp.sendError(HttpServletResponse.SC_NOT_FOUND);
0519: }
0520:
0521: }
0522:
0523: /**
0524: * Check if the conditions specified in the optional If headers are
0525: * satisfied.
0526: *
0527: * @param request The servlet request we are processing
0528: * @param response The servlet response we are creating
0529: * @param resourceAttributes The resource information
0530: * @return boolean true if the resource meets all the specified conditions,
0531: * and false if any of the conditions is not satisfied, in which case
0532: * request processing is stopped
0533: */
0534: protected boolean checkIfHeaders(HttpServletRequest request,
0535: HttpServletResponse response,
0536: ResourceAttributes resourceAttributes) throws IOException {
0537:
0538: return checkIfMatch(request, response, resourceAttributes)
0539: && checkIfModifiedSince(request, response,
0540: resourceAttributes)
0541: && checkIfNoneMatch(request, response,
0542: resourceAttributes)
0543: && checkIfUnmodifiedSince(request, response,
0544: resourceAttributes);
0545:
0546: }
0547:
0548: /**
0549: * Get the ETag associated with a file.
0550: *
0551: * @param resourceAttributes The resource information
0552: */
0553: protected String getETag(ResourceAttributes resourceAttributes) {
0554: String result = null;
0555: if ((result = resourceAttributes.getETag(true)) != null) {
0556: return result;
0557: } else if ((result = resourceAttributes.getETag()) != null) {
0558: return result;
0559: } else {
0560: return "W/\"" + resourceAttributes.getContentLength() + "-"
0561: + resourceAttributes.getLastModified() + "\"";
0562: }
0563: }
0564:
0565: /**
0566: * URL rewriter.
0567: *
0568: * @param path Path which has to be rewiten
0569: */
0570: protected String rewriteUrl(String path) {
0571: return urlEncoder.encode(path);
0572: }
0573:
0574: /**
0575: * Display the size of a file.
0576: */
0577: protected void displaySize(StringBuffer buf, int filesize) {
0578:
0579: int leftside = filesize / 1024;
0580: int rightside = (filesize % 1024) / 103; // makes 1 digit
0581: // To avoid 0.0 for non-zero file, we bump to 0.1
0582: if (leftside == 0 && rightside == 0 && filesize != 0)
0583: rightside = 1;
0584: buf.append(leftside).append(".").append(rightside);
0585: buf.append(" KB");
0586:
0587: }
0588:
0589: /**
0590: * Serve the specified resource, optionally including the data content.
0591: *
0592: * @param request The servlet request we are processing
0593: * @param response The servlet response we are creating
0594: * @param content Should the content be included?
0595: *
0596: * @exception IOException if an input/output error occurs
0597: * @exception ServletException if a servlet-specified error occurs
0598: */
0599: protected void serveResource(HttpServletRequest request,
0600: HttpServletResponse response, boolean content)
0601: throws IOException, ServletException {
0602:
0603: // Identify the requested resource path
0604: String path = getRelativePath(request);
0605: if (debug > 0) {
0606: if (content)
0607: log("DefaultServlet.serveResource: Serving resource '"
0608: + path + "' headers and data");
0609: else
0610: log("DefaultServlet.serveResource: Serving resource '"
0611: + path + "' headers only");
0612: }
0613:
0614: CacheEntry cacheEntry = resources.lookupCache(path);
0615:
0616: if (!cacheEntry.exists) {
0617: // Check if we're included so we can return the appropriate
0618: // missing resource name in the error
0619: String requestUri = (String) request
0620: .getAttribute(Globals.INCLUDE_REQUEST_URI_ATTR);
0621: if (requestUri == null) {
0622: requestUri = request.getRequestURI();
0623: } else {
0624: // We're included, and the response.sendError() below is going
0625: // to be ignored by the resource that is including us.
0626: // Therefore, the only way we can let the including resource
0627: // know is by including warning message in response
0628: response.getWriter().write(
0629: sm.getString("defaultServlet.missingResource",
0630: requestUri));
0631: }
0632:
0633: response.sendError(HttpServletResponse.SC_NOT_FOUND,
0634: requestUri);
0635: return;
0636: }
0637:
0638: // If the resource is not a collection, and the resource path
0639: // ends with "/" or "\", return NOT FOUND
0640: if (cacheEntry.context == null) {
0641: if (path.endsWith("/") || (path.endsWith("\\"))) {
0642: // Check if we're included so we can return the appropriate
0643: // missing resource name in the error
0644: String requestUri = (String) request
0645: .getAttribute(Globals.INCLUDE_REQUEST_URI_ATTR);
0646: if (requestUri == null) {
0647: requestUri = request.getRequestURI();
0648: }
0649: response.sendError(HttpServletResponse.SC_NOT_FOUND,
0650: requestUri);
0651: return;
0652: }
0653: }
0654:
0655: // Check if the conditions specified in the optional If headers are
0656: // satisfied.
0657: if (cacheEntry.context == null) {
0658:
0659: // Checking If headers
0660: boolean included = (request
0661: .getAttribute(Globals.INCLUDE_CONTEXT_PATH_ATTR) != null);
0662: if (!included
0663: && !checkIfHeaders(request, response,
0664: cacheEntry.attributes)) {
0665: return;
0666: }
0667:
0668: }
0669:
0670: // Find content type.
0671: String contentType = cacheEntry.attributes.getMimeType();
0672: if (contentType == null) {
0673: contentType = getServletContext().getMimeType(
0674: cacheEntry.name);
0675: cacheEntry.attributes.setMimeType(contentType);
0676: }
0677:
0678: ArrayList ranges = null;
0679: long contentLength = -1L;
0680:
0681: if (cacheEntry.context != null) {
0682:
0683: // Skip directory listings if we have been configured to
0684: // suppress them
0685: if (!listings) {
0686: response.sendError(HttpServletResponse.SC_NOT_FOUND,
0687: request.getRequestURI());
0688: return;
0689: }
0690: contentType = "text/html;charset=UTF-8";
0691:
0692: } else {
0693:
0694: // Parse range specifier
0695:
0696: ranges = parseRange(request, response,
0697: cacheEntry.attributes);
0698:
0699: // ETag header
0700: response.setHeader("ETag", getETag(cacheEntry.attributes));
0701:
0702: // Last-Modified header
0703: response.setHeader("Last-Modified", cacheEntry.attributes
0704: .getLastModifiedHttp());
0705:
0706: // Get content length
0707: contentLength = cacheEntry.attributes.getContentLength();
0708: // Special case for zero length files, which would cause a
0709: // (silent) ISE when setting the output buffer size
0710: if (contentLength == 0L) {
0711: content = false;
0712: }
0713:
0714: }
0715:
0716: ServletOutputStream ostream = null;
0717: PrintWriter writer = null;
0718:
0719: if (content) {
0720:
0721: // Trying to retrieve the servlet output stream
0722:
0723: try {
0724: ostream = response.getOutputStream();
0725: } catch (IllegalStateException e) {
0726: // If it fails, we try to get a Writer instead if we're
0727: // trying to serve a text file
0728: if ((contentType == null)
0729: || (contentType.startsWith("text"))
0730: || (contentType.endsWith("xml"))) {
0731: writer = response.getWriter();
0732: } else {
0733: throw e;
0734: }
0735: }
0736:
0737: }
0738:
0739: if ((cacheEntry.context != null)
0740: || (((ranges == null) || (ranges.isEmpty())) && (request
0741: .getHeader("Range") == null))
0742: || (ranges == FULL)) {
0743:
0744: // Set the appropriate output headers
0745: if (contentType != null) {
0746: if (debug > 0)
0747: log("DefaultServlet.serveFile: contentType='"
0748: + contentType + "'");
0749: response.setContentType(contentType);
0750: }
0751: if ((cacheEntry.resource != null) && (contentLength >= 0)) {
0752: if (debug > 0)
0753: log("DefaultServlet.serveFile: contentLength="
0754: + contentLength);
0755: if (contentLength < Integer.MAX_VALUE) {
0756: response.setContentLength((int) contentLength);
0757: } else {
0758: // Set the content-length as String to be able to use a long
0759: response.setHeader("content-length", ""
0760: + contentLength);
0761: }
0762: }
0763:
0764: InputStream renderResult = null;
0765: if (cacheEntry.context != null) {
0766:
0767: if (content) {
0768: // Serve the directory browser
0769: renderResult = render(request.getContextPath(),
0770: cacheEntry);
0771: }
0772:
0773: }
0774:
0775: // Copy the input stream to our output stream (if requested)
0776: if (content) {
0777: try {
0778: response.setBufferSize(output);
0779: } catch (IllegalStateException e) {
0780: // Silent catch
0781: }
0782: if (ostream != null) {
0783: if (!checkSendfile(request, response, cacheEntry,
0784: contentLength, null))
0785: copy(cacheEntry, renderResult, ostream);
0786: } else {
0787: copy(cacheEntry, renderResult, writer);
0788: }
0789: }
0790:
0791: } else {
0792:
0793: if ((ranges == null) || (ranges.isEmpty()))
0794: return;
0795:
0796: // Partial content response.
0797:
0798: response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
0799:
0800: if (ranges.size() == 1) {
0801:
0802: Range range = (Range) ranges.get(0);
0803: response.addHeader("Content-Range", "bytes "
0804: + range.start + "-" + range.end + "/"
0805: + range.length);
0806: long length = range.end - range.start + 1;
0807: if (length < Integer.MAX_VALUE) {
0808: response.setContentLength((int) length);
0809: } else {
0810: // Set the content-length as String to be able to use a long
0811: response.setHeader("content-length", "" + length);
0812: }
0813:
0814: if (contentType != null) {
0815: if (debug > 0)
0816: log("DefaultServlet.serveFile: contentType='"
0817: + contentType + "'");
0818: response.setContentType(contentType);
0819: }
0820:
0821: if (content) {
0822: try {
0823: response.setBufferSize(output);
0824: } catch (IllegalStateException e) {
0825: // Silent catch
0826: }
0827: if (ostream != null) {
0828: if (!checkSendfile(request, response,
0829: cacheEntry,
0830: range.end - range.start + 1, range))
0831: copy(cacheEntry, ostream, range);
0832: } else {
0833: copy(cacheEntry, writer, range);
0834: }
0835: }
0836:
0837: } else {
0838:
0839: response
0840: .setContentType("multipart/byteranges; boundary="
0841: + mimeSeparation);
0842:
0843: if (content) {
0844: try {
0845: response.setBufferSize(output);
0846: } catch (IllegalStateException e) {
0847: // Silent catch
0848: }
0849: if (ostream != null) {
0850: copy(cacheEntry, ostream, ranges.iterator(),
0851: contentType);
0852: } else {
0853: copy(cacheEntry, writer, ranges.iterator(),
0854: contentType);
0855: }
0856: }
0857:
0858: }
0859:
0860: }
0861:
0862: }
0863:
0864: /**
0865: * Parse the content-range header.
0866: *
0867: * @param request The servlet request we are processing
0868: * @param response The servlet response we are creating
0869: * @return Range
0870: */
0871: protected Range parseContentRange(HttpServletRequest request,
0872: HttpServletResponse response) throws IOException {
0873:
0874: // Retrieving the content-range header (if any is specified
0875: String rangeHeader = request.getHeader("Content-Range");
0876:
0877: if (rangeHeader == null)
0878: return null;
0879:
0880: // bytes is the only range unit supported
0881: if (!rangeHeader.startsWith("bytes")) {
0882: response.sendError(HttpServletResponse.SC_BAD_REQUEST);
0883: return null;
0884: }
0885:
0886: rangeHeader = rangeHeader.substring(6).trim();
0887:
0888: int dashPos = rangeHeader.indexOf('-');
0889: int slashPos = rangeHeader.indexOf('/');
0890:
0891: if (dashPos == -1) {
0892: response.sendError(HttpServletResponse.SC_BAD_REQUEST);
0893: return null;
0894: }
0895:
0896: if (slashPos == -1) {
0897: response.sendError(HttpServletResponse.SC_BAD_REQUEST);
0898: return null;
0899: }
0900:
0901: Range range = new Range();
0902:
0903: try {
0904: range.start = Long.parseLong(rangeHeader.substring(0,
0905: dashPos));
0906: range.end = Long.parseLong(rangeHeader.substring(
0907: dashPos + 1, slashPos));
0908: range.length = Long.parseLong(rangeHeader.substring(
0909: slashPos + 1, rangeHeader.length()));
0910: } catch (NumberFormatException e) {
0911: response.sendError(HttpServletResponse.SC_BAD_REQUEST);
0912: return null;
0913: }
0914:
0915: if (!range.validate()) {
0916: response.sendError(HttpServletResponse.SC_BAD_REQUEST);
0917: return null;
0918: }
0919:
0920: return range;
0921:
0922: }
0923:
0924: /**
0925: * Parse the range header.
0926: *
0927: * @param request The servlet request we are processing
0928: * @param response The servlet response we are creating
0929: * @return Vector of ranges
0930: */
0931: protected ArrayList parseRange(HttpServletRequest request,
0932: HttpServletResponse response,
0933: ResourceAttributes resourceAttributes) throws IOException {
0934:
0935: // Checking If-Range
0936: String headerValue = request.getHeader("If-Range");
0937:
0938: if (headerValue != null) {
0939:
0940: long headerValueTime = (-1L);
0941: try {
0942: headerValueTime = request.getDateHeader("If-Range");
0943: } catch (IllegalArgumentException e) {
0944: ;
0945: }
0946:
0947: String eTag = getETag(resourceAttributes);
0948: long lastModified = resourceAttributes.getLastModified();
0949:
0950: if (headerValueTime == (-1L)) {
0951:
0952: // If the ETag the client gave does not match the entity
0953: // etag, then the entire entity is returned.
0954: if (!eTag.equals(headerValue.trim()))
0955: return FULL;
0956:
0957: } else {
0958:
0959: // If the timestamp of the entity the client got is older than
0960: // the last modification date of the entity, the entire entity
0961: // is returned.
0962: if (lastModified > (headerValueTime + 1000))
0963: return FULL;
0964:
0965: }
0966:
0967: }
0968:
0969: long fileLength = resourceAttributes.getContentLength();
0970:
0971: if (fileLength == 0)
0972: return null;
0973:
0974: // Retrieving the range header (if any is specified
0975: String rangeHeader = request.getHeader("Range");
0976:
0977: if (rangeHeader == null)
0978: return null;
0979: // bytes is the only range unit supported (and I don't see the point
0980: // of adding new ones).
0981: if (!rangeHeader.startsWith("bytes")) {
0982: response
0983: .addHeader("Content-Range", "bytes */" + fileLength);
0984: response
0985: .sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
0986: return null;
0987: }
0988:
0989: rangeHeader = rangeHeader.substring(6);
0990:
0991: // Vector which will contain all the ranges which are successfully
0992: // parsed.
0993: ArrayList<Range> result = new ArrayList<Range>();
0994: StringTokenizer commaTokenizer = new StringTokenizer(
0995: rangeHeader, ",");
0996:
0997: // Parsing the range list
0998: while (commaTokenizer.hasMoreTokens()) {
0999: String rangeDefinition = commaTokenizer.nextToken().trim();
1000:
1001: Range currentRange = new Range();
1002: currentRange.length = fileLength;
1003:
1004: int dashPos = rangeDefinition.indexOf('-');
1005:
1006: if (dashPos == -1) {
1007: response.addHeader("Content-Range", "bytes */"
1008: + fileLength);
1009: response
1010: .sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
1011: return null;
1012: }
1013:
1014: if (dashPos == 0) {
1015:
1016: try {
1017: long offset = Long.parseLong(rangeDefinition);
1018: currentRange.start = fileLength + offset;
1019: currentRange.end = fileLength - 1;
1020: } catch (NumberFormatException e) {
1021: response.addHeader("Content-Range", "bytes */"
1022: + fileLength);
1023: response
1024: .sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
1025: return null;
1026: }
1027:
1028: } else {
1029:
1030: try {
1031: currentRange.start = Long.parseLong(rangeDefinition
1032: .substring(0, dashPos));
1033: if (dashPos < rangeDefinition.length() - 1)
1034: currentRange.end = Long
1035: .parseLong(rangeDefinition.substring(
1036: dashPos + 1, rangeDefinition
1037: .length()));
1038: else
1039: currentRange.end = fileLength - 1;
1040: } catch (NumberFormatException e) {
1041: response.addHeader("Content-Range", "bytes */"
1042: + fileLength);
1043: response
1044: .sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
1045: return null;
1046: }
1047:
1048: }
1049:
1050: if (!currentRange.validate()) {
1051: response.addHeader("Content-Range", "bytes */"
1052: + fileLength);
1053: response
1054: .sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
1055: return null;
1056: }
1057:
1058: result.add(currentRange);
1059: }
1060:
1061: return result;
1062: }
1063:
1064: /**
1065: * Decide which way to render. HTML or XML.
1066: */
1067: protected InputStream render(String contextPath,
1068: CacheEntry cacheEntry) throws IOException, ServletException {
1069:
1070: InputStream xsltInputStream = findXsltInputStream(cacheEntry.context);
1071:
1072: if (xsltInputStream == null) {
1073: return renderHtml(contextPath, cacheEntry);
1074: } else {
1075: return renderXml(contextPath, cacheEntry, xsltInputStream);
1076: }
1077:
1078: }
1079:
1080: /**
1081: * Return an InputStream to an HTML representation of the contents
1082: * of this directory.
1083: *
1084: * @param contextPath Context path to which our internal paths are
1085: * relative
1086: */
1087: protected InputStream renderXml(String contextPath,
1088: CacheEntry cacheEntry, InputStream xsltInputStream)
1089: throws IOException, ServletException {
1090:
1091: StringBuffer sb = new StringBuffer();
1092:
1093: sb.append("<?xml version=\"1.0\"?>");
1094: sb.append("<listing ");
1095: sb.append(" contextPath='");
1096: sb.append(contextPath);
1097: sb.append("'");
1098: sb.append(" directory='");
1099: sb.append(cacheEntry.name);
1100: sb.append("' ");
1101: sb.append(" hasParent='").append(!cacheEntry.name.equals("/"));
1102: sb.append("'>");
1103:
1104: sb.append("<entries>");
1105:
1106: try {
1107:
1108: // Render the directory entries within this directory
1109: NamingEnumeration enumeration = resources
1110: .list(cacheEntry.name);
1111:
1112: // rewriteUrl(contextPath) is expensive. cache result for later reuse
1113: String rewrittenContextPath = rewriteUrl(contextPath);
1114:
1115: while (enumeration.hasMoreElements()) {
1116:
1117: NameClassPair ncPair = (NameClassPair) enumeration
1118: .nextElement();
1119: String resourceName = ncPair.getName();
1120: String trimmed = resourceName/*.substring(trim)*/;
1121: if (trimmed.equalsIgnoreCase("WEB-INF")
1122: || trimmed.equalsIgnoreCase("META-INF")
1123: || trimmed.equalsIgnoreCase(localXsltFile))
1124: continue;
1125:
1126: CacheEntry childCacheEntry = resources
1127: .lookupCache(cacheEntry.name + resourceName);
1128: if (!childCacheEntry.exists) {
1129: continue;
1130: }
1131:
1132: sb.append("<entry");
1133: sb.append(" type='").append(
1134: (childCacheEntry.context != null) ? "dir"
1135: : "file").append("'");
1136: sb.append(" urlPath='").append(rewrittenContextPath)
1137: .append(
1138: rewriteUrl(cacheEntry.name
1139: + resourceName)).append(
1140: (childCacheEntry.context != null) ? "/"
1141: : "").append("'");
1142: if (childCacheEntry.resource != null) {
1143: sb.append(" size='").append(
1144: renderSize(childCacheEntry.attributes
1145: .getContentLength())).append("'");
1146: }
1147: sb.append(" date='").append(
1148: childCacheEntry.attributes
1149: .getLastModifiedHttp()).append("'");
1150:
1151: sb.append(">");
1152: sb.append(RequestUtil.filter(trimmed));
1153: if (childCacheEntry.context != null)
1154: sb.append("/");
1155: sb.append("</entry>");
1156:
1157: }
1158:
1159: } catch (NamingException e) {
1160: // Something went wrong
1161: throw new ServletException("Error accessing resource", e);
1162: }
1163:
1164: sb.append("</entries>");
1165:
1166: String readme = getReadme(cacheEntry.context);
1167:
1168: if (readme != null) {
1169: sb.append("<readme><![CDATA[");
1170: sb.append(readme);
1171: sb.append("]]></readme>");
1172: }
1173:
1174: sb.append("</listing>");
1175:
1176: try {
1177: TransformerFactory tFactory = TransformerFactory
1178: .newInstance();
1179: Source xmlSource = new StreamSource(new StringReader(sb
1180: .toString()));
1181: Source xslSource = new StreamSource(xsltInputStream);
1182: Transformer transformer = tFactory
1183: .newTransformer(xslSource);
1184:
1185: ByteArrayOutputStream stream = new ByteArrayOutputStream();
1186: OutputStreamWriter osWriter = new OutputStreamWriter(
1187: stream, "UTF8");
1188: StreamResult out = new StreamResult(osWriter);
1189: transformer.transform(xmlSource, out);
1190: osWriter.flush();
1191: return (new ByteArrayInputStream(stream.toByteArray()));
1192: } catch (TransformerException e) {
1193: throw new ServletException("XSL transformer error", e);
1194: }
1195: }
1196:
1197: /**
1198: * Return an InputStream to an HTML representation of the contents
1199: * of this directory.
1200: *
1201: * @param contextPath Context path to which our internal paths are
1202: * relative
1203: */
1204: protected InputStream renderHtml(String contextPath,
1205: CacheEntry cacheEntry) throws IOException, ServletException {
1206:
1207: String name = cacheEntry.name;
1208:
1209: // Number of characters to trim from the beginnings of filenames
1210: int trim = name.length();
1211: if (!name.endsWith("/"))
1212: trim += 1;
1213: if (name.equals("/"))
1214: trim = 1;
1215:
1216: // Prepare a writer to a buffered area
1217: ByteArrayOutputStream stream = new ByteArrayOutputStream();
1218: OutputStreamWriter osWriter = new OutputStreamWriter(stream,
1219: "UTF8");
1220: PrintWriter writer = new PrintWriter(osWriter);
1221:
1222: StringBuffer sb = new StringBuffer();
1223:
1224: // rewriteUrl(contextPath) is expensive. cache result for later reuse
1225: String rewrittenContextPath = rewriteUrl(contextPath);
1226:
1227: // Render the page header
1228: sb.append("<html>\r\n");
1229: sb.append("<head>\r\n");
1230: sb.append("<title>");
1231: sb.append(sm.getString("directory.title", name));
1232: sb.append("</title>\r\n");
1233: sb.append("<STYLE><!--");
1234: sb.append(org.apache.catalina.util.TomcatCSS.TOMCAT_CSS);
1235: sb.append("--></STYLE> ");
1236: sb.append("</head>\r\n");
1237: sb.append("<body>");
1238: sb.append("<h1>");
1239: sb.append(sm.getString("directory.title", name));
1240:
1241: // Render the link to our parent (if required)
1242: String parentDirectory = name;
1243: if (parentDirectory.endsWith("/")) {
1244: parentDirectory = parentDirectory.substring(0,
1245: parentDirectory.length() - 1);
1246: }
1247: int slash = parentDirectory.lastIndexOf('/');
1248: if (slash >= 0) {
1249: String parent = name.substring(0, slash);
1250: sb.append(" - <a href=\"");
1251: sb.append(rewrittenContextPath);
1252: if (parent.equals(""))
1253: parent = "/";
1254: sb.append(rewriteUrl(parent));
1255: if (!parent.endsWith("/"))
1256: sb.append("/");
1257: sb.append("\">");
1258: sb.append("<b>");
1259: sb.append(sm.getString("directory.parent", parent));
1260: sb.append("</b>");
1261: sb.append("</a>");
1262: }
1263:
1264: sb.append("</h1>");
1265: sb.append("<HR size=\"1\" noshade=\"noshade\">");
1266:
1267: sb.append("<table width=\"100%\" cellspacing=\"0\""
1268: + " cellpadding=\"5\" align=\"center\">\r\n");
1269:
1270: // Render the column headings
1271: sb.append("<tr>\r\n");
1272: sb.append("<td align=\"left\"><font size=\"+1\"><strong>");
1273: sb.append(sm.getString("directory.filename"));
1274: sb.append("</strong></font></td>\r\n");
1275: sb.append("<td align=\"center\"><font size=\"+1\"><strong>");
1276: sb.append(sm.getString("directory.size"));
1277: sb.append("</strong></font></td>\r\n");
1278: sb.append("<td align=\"right\"><font size=\"+1\"><strong>");
1279: sb.append(sm.getString("directory.lastModified"));
1280: sb.append("</strong></font></td>\r\n");
1281: sb.append("</tr>");
1282:
1283: try {
1284:
1285: // Render the directory entries within this directory
1286: NamingEnumeration enumeration = resources
1287: .list(cacheEntry.name);
1288: boolean shade = false;
1289: while (enumeration.hasMoreElements()) {
1290:
1291: NameClassPair ncPair = (NameClassPair) enumeration
1292: .nextElement();
1293: String resourceName = ncPair.getName();
1294: String trimmed = resourceName/*.substring(trim)*/;
1295: if (trimmed.equalsIgnoreCase("WEB-INF")
1296: || trimmed.equalsIgnoreCase("META-INF"))
1297: continue;
1298:
1299: CacheEntry childCacheEntry = resources
1300: .lookupCache(cacheEntry.name + resourceName);
1301: if (!childCacheEntry.exists) {
1302: continue;
1303: }
1304:
1305: sb.append("<tr");
1306: if (shade)
1307: sb.append(" bgcolor=\"#eeeeee\"");
1308: sb.append(">\r\n");
1309: shade = !shade;
1310:
1311: sb.append("<td align=\"left\"> \r\n");
1312: sb.append("<a href=\"");
1313: sb.append(rewrittenContextPath);
1314: resourceName = rewriteUrl(name + resourceName);
1315: sb.append(resourceName);
1316: if (childCacheEntry.context != null)
1317: sb.append("/");
1318: sb.append("\"><tt>");
1319: sb.append(RequestUtil.filter(trimmed));
1320: if (childCacheEntry.context != null)
1321: sb.append("/");
1322: sb.append("</tt></a></td>\r\n");
1323:
1324: sb.append("<td align=\"right\"><tt>");
1325: if (childCacheEntry.context != null)
1326: sb.append(" ");
1327: else
1328: sb.append(renderSize(childCacheEntry.attributes
1329: .getContentLength()));
1330: sb.append("</tt></td>\r\n");
1331:
1332: sb.append("<td align=\"right\"><tt>");
1333: sb.append(childCacheEntry.attributes
1334: .getLastModifiedHttp());
1335: sb.append("</tt></td>\r\n");
1336:
1337: sb.append("</tr>\r\n");
1338: }
1339:
1340: } catch (NamingException e) {
1341: // Something went wrong
1342: throw new ServletException("Error accessing resource", e);
1343: }
1344:
1345: // Render the page footer
1346: sb.append("</table>\r\n");
1347:
1348: sb.append("<HR size=\"1\" noshade=\"noshade\">");
1349:
1350: String readme = getReadme(cacheEntry.context);
1351: if (readme != null) {
1352: sb.append(readme);
1353: sb.append("<HR size=\"1\" noshade=\"noshade\">");
1354: }
1355:
1356: sb.append("<h3>").append(ServerInfo.getServerInfo()).append(
1357: "</h3>");
1358: sb.append("</body>\r\n");
1359: sb.append("</html>\r\n");
1360:
1361: // Return an input stream to the underlying bytes
1362: writer.write(sb.toString());
1363: writer.flush();
1364: return (new ByteArrayInputStream(stream.toByteArray()));
1365:
1366: }
1367:
1368: /**
1369: * Render the specified file size (in bytes).
1370: *
1371: * @param size File size (in bytes)
1372: */
1373: protected String renderSize(long size) {
1374:
1375: long leftSide = size / 1024;
1376: long rightSide = (size % 1024) / 103; // Makes 1 digit
1377: if ((leftSide == 0) && (rightSide == 0) && (size > 0))
1378: rightSide = 1;
1379:
1380: return ("" + leftSide + "." + rightSide + " kb");
1381:
1382: }
1383:
1384: /**
1385: * Get the readme file as a string.
1386: */
1387: protected String getReadme(DirContext directory)
1388: throws IOException, ServletException {
1389:
1390: if (readmeFile != null) {
1391: try {
1392: Object obj = directory.lookup(readmeFile);
1393: if ((obj != null) && (obj instanceof Resource)) {
1394: StringWriter buffer = new StringWriter();
1395: InputStream is = ((Resource) obj).streamContent();
1396: copyRange(new InputStreamReader(is),
1397: new PrintWriter(buffer));
1398: return buffer.toString();
1399: }
1400: } catch (NamingException e) {
1401: throw new ServletException(
1402: "Error opening readme resource", e);
1403: }
1404: }
1405:
1406: return null;
1407: }
1408:
1409: /**
1410: * Return the xsl template inputstream (if possible)
1411: */
1412: protected InputStream findXsltInputStream(DirContext directory)
1413: throws IOException, ServletException {
1414:
1415: if (localXsltFile != null) {
1416: try {
1417: Object obj = directory.lookup(localXsltFile);
1418: if ((obj != null) && (obj instanceof Resource)) {
1419: InputStream is = ((Resource) obj).streamContent();
1420: if (is != null)
1421: return is;
1422: }
1423: } catch (NamingException e) {
1424: throw new ServletException(
1425: "Error opening XSLT resource", e);
1426: }
1427: }
1428:
1429: /* Open and read in file in one fell swoop to reduce chance
1430: * chance of leaving handle open.
1431: */
1432: if (globalXsltFile != null) {
1433: FileInputStream fis = null;
1434:
1435: try {
1436: File f = new File(globalXsltFile);
1437: if (f.exists()) {
1438: fis = new FileInputStream(f);
1439: byte b[] = new byte[(int) f.length()]; /* danger! */
1440: fis.read(b);
1441: return new ByteArrayInputStream(b);
1442: }
1443: } finally {
1444: if (fis != null)
1445: fis.close();
1446: }
1447: }
1448:
1449: return null;
1450:
1451: }
1452:
1453: // -------------------------------------------------------- protected Methods
1454:
1455: /**
1456: * Check if sendfile can be used.
1457: */
1458: protected boolean checkSendfile(HttpServletRequest request,
1459: HttpServletResponse response, CacheEntry entry,
1460: long length, Range range) {
1461: if ((sendfileSize > 0)
1462: && (entry.resource != null)
1463: && ((length > sendfileSize) || (entry.resource
1464: .getContent() == null))
1465: && (entry.attributes.getCanonicalPath() != null)
1466: && (Boolean.TRUE == request
1467: .getAttribute("org.apache.tomcat.sendfile.support"))
1468: && (request.getClass().getName()
1469: .equals("org.apache.catalina.connector.RequestFacade"))
1470: && (response.getClass().getName()
1471: .equals("org.apache.catalina.connector.ResponseFacade"))) {
1472: request.setAttribute("org.apache.tomcat.sendfile.filename",
1473: entry.attributes.getCanonicalPath());
1474: if (range == null) {
1475: request.setAttribute(
1476: "org.apache.tomcat.sendfile.start",
1477: new Long(0L));
1478: request.setAttribute("org.apache.tomcat.sendfile.end",
1479: new Long(length));
1480: } else {
1481: request.setAttribute(
1482: "org.apache.tomcat.sendfile.start", new Long(
1483: range.start));
1484: request.setAttribute("org.apache.tomcat.sendfile.end",
1485: new Long(range.end + 1));
1486: }
1487: request.setAttribute("org.apache.tomcat.sendfile.token",
1488: this );
1489: return true;
1490: } else {
1491: return false;
1492: }
1493: }
1494:
1495: /**
1496: * Check if the if-match condition is satisfied.
1497: *
1498: * @param request The servlet request we are processing
1499: * @param response The servlet response we are creating
1500: * @param resourceInfo File object
1501: * @return boolean true if the resource meets the specified condition,
1502: * and false if the condition is not satisfied, in which case request
1503: * processing is stopped
1504: */
1505: protected boolean checkIfMatch(HttpServletRequest request,
1506: HttpServletResponse response,
1507: ResourceAttributes resourceAttributes) throws IOException {
1508:
1509: String eTag = getETag(resourceAttributes);
1510: String headerValue = request.getHeader("If-Match");
1511: if (headerValue != null) {
1512: if (headerValue.indexOf('*') == -1) {
1513:
1514: StringTokenizer commaTokenizer = new StringTokenizer(
1515: headerValue, ",");
1516: boolean conditionSatisfied = false;
1517:
1518: while (!conditionSatisfied
1519: && commaTokenizer.hasMoreTokens()) {
1520: String currentToken = commaTokenizer.nextToken();
1521: if (currentToken.trim().equals(eTag))
1522: conditionSatisfied = true;
1523: }
1524:
1525: // If none of the given ETags match, 412 Precodition failed is
1526: // sent back
1527: if (!conditionSatisfied) {
1528: response
1529: .sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
1530: return false;
1531: }
1532:
1533: }
1534: }
1535: return true;
1536:
1537: }
1538:
1539: /**
1540: * Check if the if-modified-since condition is satisfied.
1541: *
1542: * @param request The servlet request we are processing
1543: * @param response The servlet response we are creating
1544: * @param resourceInfo File object
1545: * @return boolean true if the resource meets the specified condition,
1546: * and false if the condition is not satisfied, in which case request
1547: * processing is stopped
1548: */
1549: protected boolean checkIfModifiedSince(HttpServletRequest request,
1550: HttpServletResponse response,
1551: ResourceAttributes resourceAttributes) throws IOException {
1552: try {
1553: long headerValue = request
1554: .getDateHeader("If-Modified-Since");
1555: long lastModified = resourceAttributes.getLastModified();
1556: if (headerValue != -1) {
1557:
1558: // If an If-None-Match header has been specified, if modified since
1559: // is ignored.
1560: if ((request.getHeader("If-None-Match") == null)
1561: && (lastModified < headerValue + 1000)) {
1562: // The entity has not been modified since the date
1563: // specified by the client. This is not an error case.
1564: response
1565: .setStatus(HttpServletResponse.SC_NOT_MODIFIED);
1566: response.setHeader("ETag",
1567: getETag(resourceAttributes));
1568:
1569: return false;
1570: }
1571: }
1572: } catch (IllegalArgumentException illegalArgument) {
1573: return true;
1574: }
1575: return true;
1576:
1577: }
1578:
1579: /**
1580: * Check if the if-none-match condition is satisfied.
1581: *
1582: * @param request The servlet request we are processing
1583: * @param response The servlet response we are creating
1584: * @param resourceInfo File object
1585: * @return boolean true if the resource meets the specified condition,
1586: * and false if the condition is not satisfied, in which case request
1587: * processing is stopped
1588: */
1589: protected boolean checkIfNoneMatch(HttpServletRequest request,
1590: HttpServletResponse response,
1591: ResourceAttributes resourceAttributes) throws IOException {
1592:
1593: String eTag = getETag(resourceAttributes);
1594: String headerValue = request.getHeader("If-None-Match");
1595: if (headerValue != null) {
1596:
1597: boolean conditionSatisfied = false;
1598:
1599: if (!headerValue.equals("*")) {
1600:
1601: StringTokenizer commaTokenizer = new StringTokenizer(
1602: headerValue, ",");
1603:
1604: while (!conditionSatisfied
1605: && commaTokenizer.hasMoreTokens()) {
1606: String currentToken = commaTokenizer.nextToken();
1607: if (currentToken.trim().equals(eTag))
1608: conditionSatisfied = true;
1609: }
1610:
1611: } else {
1612: conditionSatisfied = true;
1613: }
1614:
1615: if (conditionSatisfied) {
1616:
1617: // For GET and HEAD, we should respond with
1618: // 304 Not Modified.
1619: // For every other method, 412 Precondition Failed is sent
1620: // back.
1621: if (("GET".equals(request.getMethod()))
1622: || ("HEAD".equals(request.getMethod()))) {
1623: response
1624: .setStatus(HttpServletResponse.SC_NOT_MODIFIED);
1625: response.setHeader("ETag",
1626: getETag(resourceAttributes));
1627:
1628: return false;
1629: } else {
1630: response
1631: .sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
1632: return false;
1633: }
1634: }
1635: }
1636: return true;
1637:
1638: }
1639:
1640: /**
1641: * Check if the if-unmodified-since condition is satisfied.
1642: *
1643: * @param request The servlet request we are processing
1644: * @param response The servlet response we are creating
1645: * @param resourceInfo File object
1646: * @return boolean true if the resource meets the specified condition,
1647: * and false if the condition is not satisfied, in which case request
1648: * processing is stopped
1649: */
1650: protected boolean checkIfUnmodifiedSince(
1651: HttpServletRequest request, HttpServletResponse response,
1652: ResourceAttributes resourceAttributes) throws IOException {
1653: try {
1654: long lastModified = resourceAttributes.getLastModified();
1655: long headerValue = request
1656: .getDateHeader("If-Unmodified-Since");
1657: if (headerValue != -1) {
1658: if (lastModified >= (headerValue + 1000)) {
1659: // The entity has not been modified since the date
1660: // specified by the client. This is not an error case.
1661: response
1662: .sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
1663: return false;
1664: }
1665: }
1666: } catch (IllegalArgumentException illegalArgument) {
1667: return true;
1668: }
1669: return true;
1670:
1671: }
1672:
1673: /**
1674: * Copy the contents of the specified input stream to the specified
1675: * output stream, and ensure that both streams are closed before returning
1676: * (even in the face of an exception).
1677: *
1678: * @param resourceInfo The resource information
1679: * @param ostream The output stream to write to
1680: *
1681: * @exception IOException if an input/output error occurs
1682: */
1683: protected void copy(CacheEntry cacheEntry, InputStream is,
1684: ServletOutputStream ostream) throws IOException {
1685:
1686: IOException exception = null;
1687: InputStream resourceInputStream = null;
1688:
1689: // Optimization: If the binary content has already been loaded, send
1690: // it directly
1691: if (cacheEntry.resource != null) {
1692: byte buffer[] = cacheEntry.resource.getContent();
1693: if (buffer != null) {
1694: ostream.write(buffer, 0, buffer.length);
1695: return;
1696: }
1697: resourceInputStream = cacheEntry.resource.streamContent();
1698: } else {
1699: resourceInputStream = is;
1700: }
1701:
1702: InputStream istream = new BufferedInputStream(
1703: resourceInputStream, input);
1704:
1705: // Copy the input stream to the output stream
1706: exception = copyRange(istream, ostream);
1707:
1708: // Clean up the input stream
1709: istream.close();
1710:
1711: // Rethrow any exception that has occurred
1712: if (exception != null)
1713: throw exception;
1714:
1715: }
1716:
1717: /**
1718: * Copy the contents of the specified input stream to the specified
1719: * output stream, and ensure that both streams are closed before returning
1720: * (even in the face of an exception).
1721: *
1722: * @param resourceInfo The resource info
1723: * @param writer The writer to write to
1724: *
1725: * @exception IOException if an input/output error occurs
1726: */
1727: protected void copy(CacheEntry cacheEntry, InputStream is,
1728: PrintWriter writer) throws IOException {
1729:
1730: IOException exception = null;
1731:
1732: InputStream resourceInputStream = null;
1733: if (cacheEntry.resource != null) {
1734: resourceInputStream = cacheEntry.resource.streamContent();
1735: } else {
1736: resourceInputStream = is;
1737: }
1738:
1739: Reader reader;
1740: if (fileEncoding == null) {
1741: reader = new InputStreamReader(resourceInputStream);
1742: } else {
1743: reader = new InputStreamReader(resourceInputStream,
1744: fileEncoding);
1745: }
1746:
1747: // Copy the input stream to the output stream
1748: exception = copyRange(reader, writer);
1749:
1750: // Clean up the reader
1751: reader.close();
1752:
1753: // Rethrow any exception that has occurred
1754: if (exception != null)
1755: throw exception;
1756:
1757: }
1758:
1759: /**
1760: * Copy the contents of the specified input stream to the specified
1761: * output stream, and ensure that both streams are closed before returning
1762: * (even in the face of an exception).
1763: *
1764: * @param resourceInfo The ResourceInfo object
1765: * @param ostream The output stream to write to
1766: * @param range Range the client wanted to retrieve
1767: * @exception IOException if an input/output error occurs
1768: */
1769: protected void copy(CacheEntry cacheEntry,
1770: ServletOutputStream ostream, Range range)
1771: throws IOException {
1772:
1773: IOException exception = null;
1774:
1775: InputStream resourceInputStream = cacheEntry.resource
1776: .streamContent();
1777: InputStream istream = new BufferedInputStream(
1778: resourceInputStream, input);
1779: exception = copyRange(istream, ostream, range.start, range.end);
1780:
1781: // Clean up the input stream
1782: istream.close();
1783:
1784: // Rethrow any exception that has occurred
1785: if (exception != null)
1786: throw exception;
1787:
1788: }
1789:
1790: /**
1791: * Copy the contents of the specified input stream to the specified
1792: * output stream, and ensure that both streams are closed before returning
1793: * (even in the face of an exception).
1794: *
1795: * @param resourceInfo The ResourceInfo object
1796: * @param writer The writer to write to
1797: * @param range Range the client wanted to retrieve
1798: * @exception IOException if an input/output error occurs
1799: */
1800: protected void copy(CacheEntry cacheEntry, PrintWriter writer,
1801: Range range) throws IOException {
1802:
1803: IOException exception = null;
1804:
1805: InputStream resourceInputStream = cacheEntry.resource
1806: .streamContent();
1807:
1808: Reader reader;
1809: if (fileEncoding == null) {
1810: reader = new InputStreamReader(resourceInputStream);
1811: } else {
1812: reader = new InputStreamReader(resourceInputStream,
1813: fileEncoding);
1814: }
1815:
1816: exception = copyRange(reader, writer, range.start, range.end);
1817:
1818: // Clean up the input stream
1819: reader.close();
1820:
1821: // Rethrow any exception that has occurred
1822: if (exception != null)
1823: throw exception;
1824:
1825: }
1826:
1827: /**
1828: * Copy the contents of the specified input stream to the specified
1829: * output stream, and ensure that both streams are closed before returning
1830: * (even in the face of an exception).
1831: *
1832: * @param resourceInfo The ResourceInfo object
1833: * @param ostream The output stream to write to
1834: * @param ranges Enumeration of the ranges the client wanted to retrieve
1835: * @param contentType Content type of the resource
1836: * @exception IOException if an input/output error occurs
1837: */
1838: protected void copy(CacheEntry cacheEntry,
1839: ServletOutputStream ostream, Iterator ranges,
1840: String contentType) throws IOException {
1841:
1842: IOException exception = null;
1843:
1844: while ((exception == null) && (ranges.hasNext())) {
1845:
1846: InputStream resourceInputStream = cacheEntry.resource
1847: .streamContent();
1848: InputStream istream = new BufferedInputStream(
1849: resourceInputStream, input);
1850:
1851: Range currentRange = (Range) ranges.next();
1852:
1853: // Writing MIME header.
1854: ostream.println();
1855: ostream.println("--" + mimeSeparation);
1856: if (contentType != null)
1857: ostream.println("Content-Type: " + contentType);
1858: ostream.println("Content-Range: bytes "
1859: + currentRange.start + "-" + currentRange.end + "/"
1860: + currentRange.length);
1861: ostream.println();
1862:
1863: // Printing content
1864: exception = copyRange(istream, ostream, currentRange.start,
1865: currentRange.end);
1866:
1867: istream.close();
1868:
1869: }
1870:
1871: ostream.println();
1872: ostream.print("--" + mimeSeparation + "--");
1873:
1874: // Rethrow any exception that has occurred
1875: if (exception != null)
1876: throw exception;
1877:
1878: }
1879:
1880: /**
1881: * Copy the contents of the specified input stream to the specified
1882: * output stream, and ensure that both streams are closed before returning
1883: * (even in the face of an exception).
1884: *
1885: * @param resourceInfo The ResourceInfo object
1886: * @param writer The writer to write to
1887: * @param ranges Enumeration of the ranges the client wanted to retrieve
1888: * @param contentType Content type of the resource
1889: * @exception IOException if an input/output error occurs
1890: */
1891: protected void copy(CacheEntry cacheEntry, PrintWriter writer,
1892: Iterator ranges, String contentType) throws IOException {
1893:
1894: IOException exception = null;
1895:
1896: while ((exception == null) && (ranges.hasNext())) {
1897:
1898: InputStream resourceInputStream = cacheEntry.resource
1899: .streamContent();
1900:
1901: Reader reader;
1902: if (fileEncoding == null) {
1903: reader = new InputStreamReader(resourceInputStream);
1904: } else {
1905: reader = new InputStreamReader(resourceInputStream,
1906: fileEncoding);
1907: }
1908:
1909: Range currentRange = (Range) ranges.next();
1910:
1911: // Writing MIME header.
1912: writer.println();
1913: writer.println("--" + mimeSeparation);
1914: if (contentType != null)
1915: writer.println("Content-Type: " + contentType);
1916: writer.println("Content-Range: bytes " + currentRange.start
1917: + "-" + currentRange.end + "/"
1918: + currentRange.length);
1919: writer.println();
1920:
1921: // Printing content
1922: exception = copyRange(reader, writer, currentRange.start,
1923: currentRange.end);
1924:
1925: reader.close();
1926:
1927: }
1928:
1929: writer.println();
1930: writer.print("--" + mimeSeparation + "--");
1931:
1932: // Rethrow any exception that has occurred
1933: if (exception != null)
1934: throw exception;
1935:
1936: }
1937:
1938: /**
1939: * Copy the contents of the specified input stream to the specified
1940: * output stream, and ensure that both streams are closed before returning
1941: * (even in the face of an exception).
1942: *
1943: * @param istream The input stream to read from
1944: * @param ostream The output stream to write to
1945: * @return Exception which occurred during processing
1946: */
1947: protected IOException copyRange(InputStream istream,
1948: ServletOutputStream ostream) {
1949:
1950: // Copy the input stream to the output stream
1951: IOException exception = null;
1952: byte buffer[] = new byte[input];
1953: int len = buffer.length;
1954: while (true) {
1955: try {
1956: len = istream.read(buffer);
1957: if (len == -1)
1958: break;
1959: ostream.write(buffer, 0, len);
1960: } catch (IOException e) {
1961: exception = e;
1962: len = -1;
1963: break;
1964: }
1965: }
1966: return exception;
1967:
1968: }
1969:
1970: /**
1971: * Copy the contents of the specified input stream to the specified
1972: * output stream, and ensure that both streams are closed before returning
1973: * (even in the face of an exception).
1974: *
1975: * @param reader The reader to read from
1976: * @param writer The writer to write to
1977: * @return Exception which occurred during processing
1978: */
1979: protected IOException copyRange(Reader reader, PrintWriter writer) {
1980:
1981: // Copy the input stream to the output stream
1982: IOException exception = null;
1983: char buffer[] = new char[input];
1984: int len = buffer.length;
1985: while (true) {
1986: try {
1987: len = reader.read(buffer);
1988: if (len == -1)
1989: break;
1990: writer.write(buffer, 0, len);
1991: } catch (IOException e) {
1992: exception = e;
1993: len = -1;
1994: break;
1995: }
1996: }
1997: return exception;
1998:
1999: }
2000:
2001: /**
2002: * Copy the contents of the specified input stream to the specified
2003: * output stream, and ensure that both streams are closed before returning
2004: * (even in the face of an exception).
2005: *
2006: * @param istream The input stream to read from
2007: * @param ostream The output stream to write to
2008: * @param start Start of the range which will be copied
2009: * @param end End of the range which will be copied
2010: * @return Exception which occurred during processing
2011: */
2012: protected IOException copyRange(InputStream istream,
2013: ServletOutputStream ostream, long start, long end) {
2014:
2015: if (debug > 10)
2016: log("Serving bytes:" + start + "-" + end);
2017:
2018: try {
2019: istream.skip(start);
2020: } catch (IOException e) {
2021: return e;
2022: }
2023:
2024: IOException exception = null;
2025: long bytesToRead = end - start + 1;
2026:
2027: byte buffer[] = new byte[input];
2028: int len = buffer.length;
2029: while ((bytesToRead > 0) && (len >= buffer.length)) {
2030: try {
2031: len = istream.read(buffer);
2032: if (bytesToRead >= len) {
2033: ostream.write(buffer, 0, len);
2034: bytesToRead -= len;
2035: } else {
2036: ostream.write(buffer, 0, (int) bytesToRead);
2037: bytesToRead = 0;
2038: }
2039: } catch (IOException e) {
2040: exception = e;
2041: len = -1;
2042: }
2043: if (len < buffer.length)
2044: break;
2045: }
2046:
2047: return exception;
2048:
2049: }
2050:
2051: /**
2052: * Copy the contents of the specified input stream to the specified
2053: * output stream, and ensure that both streams are closed before returning
2054: * (even in the face of an exception).
2055: *
2056: * @param reader The reader to read from
2057: * @param writer The writer to write to
2058: * @param start Start of the range which will be copied
2059: * @param end End of the range which will be copied
2060: * @return Exception which occurred during processing
2061: */
2062: protected IOException copyRange(Reader reader, PrintWriter writer,
2063: long start, long end) {
2064:
2065: try {
2066: reader.skip(start);
2067: } catch (IOException e) {
2068: return e;
2069: }
2070:
2071: IOException exception = null;
2072: long bytesToRead = end - start + 1;
2073:
2074: char buffer[] = new char[input];
2075: int len = buffer.length;
2076: while ((bytesToRead > 0) && (len >= buffer.length)) {
2077: try {
2078: len = reader.read(buffer);
2079: if (bytesToRead >= len) {
2080: writer.write(buffer, 0, len);
2081: bytesToRead -= len;
2082: } else {
2083: writer.write(buffer, 0, (int) bytesToRead);
2084: bytesToRead = 0;
2085: }
2086: } catch (IOException e) {
2087: exception = e;
2088: len = -1;
2089: }
2090: if (len < buffer.length)
2091: break;
2092: }
2093:
2094: return exception;
2095:
2096: }
2097:
2098: // ------------------------------------------------------ Range Inner Class
2099:
2100: protected class Range {
2101:
2102: public long start;
2103: public long end;
2104: public long length;
2105:
2106: /**
2107: * Validate range.
2108: */
2109: public boolean validate() {
2110: if (end >= length)
2111: end = length - 1;
2112: return ((start >= 0) && (end >= 0) && (start <= end) && (length > 0));
2113: }
2114:
2115: public void recycle() {
2116: start = 0;
2117: end = 0;
2118: length = 0;
2119: }
2120:
2121: }
2122:
2123: }
|