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