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