You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by co...@apache.org on 2008/08/30 06:25:53 UTC
svn commit: r690458 [1/2] -
/tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/
Author: costin
Date: Fri Aug 29 21:25:51 2008
New Revision: 690458
URL: http://svn.apache.org/viewvc?rev=690458&view=rev
Log:
Ported the default servlet and dav servlet to plain coyote
Added:
tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/
tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/CopyUtils.java (with props)
tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/Dir2Html.java (with props)
tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/FileAdapter.java (with props)
tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/LocalStrings.properties (with props)
tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/Range.java (with props)
tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/URLEncoder.java (with props)
tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/UrlUtils.java (with props)
tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/WebdavAdapter.java (with props)
tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/XMLWriter.java (with props)
Added: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/CopyUtils.java
URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/CopyUtils.java?rev=690458&view=auto
==============================================================================
--- tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/CopyUtils.java (added)
+++ tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/CopyUtils.java Fri Aug 29 21:25:51 2008
@@ -0,0 +1,292 @@
+package org.apache.coyote.adapters.dav;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.Reader;
+
+import org.apache.coyote.adapters.MessageWriter;
+
+public class CopyUtils {
+ protected static int input = 2048;
+
+ /**
+ * Copy the contents of the specified input stream to the specified
+ * output stream, and ensure that both streams are closed before returning
+ * (even in the face of an exception).
+ *
+ * @param resourceInfo The resource info
+ * @param writer The writer to write to
+ *
+ * @exception IOException if an input/output error occurs
+ */
+// public static void copy(InputStream is,
+// PrintWriter writer,
+// String fileEncoding)
+// throws IOException {
+//
+// IOException exception = null;
+//
+// InputStream resourceInputStream = is;
+//
+// Reader reader;
+// if (fileEncoding == null) {
+// reader = new InputStreamReader(resourceInputStream);
+// } else {
+// reader = new InputStreamReader(resourceInputStream,
+// fileEncoding);
+// }
+//
+// // Copy the input stream to the output stream
+// exception = copyRange(reader, writer);
+//
+// // Clean up the reader
+// try {
+// reader.close();
+// } catch (Throwable t) {
+// ;
+// }
+//
+// // Rethrow any exception that has occurred
+// if (exception != null)
+// throw exception;
+//
+// }
+
+ /**
+ * Copy the contents of the specified input stream to the specified
+ * output stream, and ensure that both streams are closed before returning
+ * (even in the face of an exception).
+ *
+ * @param resourceInfo The resource information
+ * @param ostream The output stream to write to
+ *
+ * @exception IOException if an input/output error occurs
+ */
+ public static void copy(InputStream is, OutputStream ostream)
+ throws IOException {
+
+ IOException exception = null;
+ InputStream resourceInputStream = null;
+
+ resourceInputStream = is;
+
+ InputStream istream = new BufferedInputStream
+ (resourceInputStream, input);
+
+ // Copy the input stream to the output stream
+ exception = CopyUtils.copyRange(istream, ostream);
+
+ // Clean up the input stream
+ try {
+ istream.close();
+ } catch (Throwable t) {
+ ;
+ }
+
+ // Rethrow any exception that has occurred
+ if (exception != null)
+ throw exception;
+ }
+
+ /**
+ * Copy the contents of the specified input stream to the specified
+ * output stream, and ensure that both streams are closed before returning
+ * (even in the face of an exception).
+ *
+ * @param istream The input stream to read from
+ * @param ostream The output stream to write to
+ * @return Exception which occurred during processing
+ */
+ public static IOException copyRange(InputStream istream,
+ OutputStream ostream) {
+
+ // Copy the input stream to the output stream
+ IOException exception = null;
+ byte buffer[] = new byte[input];
+ int len = buffer.length;
+ while (true) {
+ try {
+ len = istream.read(buffer);
+ if (len == -1)
+ break;
+ ostream.write(buffer, 0, len);
+ } catch (IOException e) {
+ exception = e;
+ len = -1;
+ break;
+ }
+ }
+ return exception;
+
+ }
+
+
+ /**
+ * Copy the contents of the specified input stream to the specified
+ * output stream, and ensure that both streams are closed before returning
+ * (even in the face of an exception).
+ *
+ * @param reader The reader to read from
+ * @param writer The writer to write to
+ * @return Exception which occurred during processing
+ */
+ public static IOException copyRange(Reader reader, PrintWriter writer) {
+
+ // Copy the input stream to the output stream
+ IOException exception = null;
+ char buffer[] = new char[input];
+ int len = buffer.length;
+ while (true) {
+ try {
+ len = reader.read(buffer);
+ if (len == -1)
+ break;
+ writer.write(buffer, 0, len);
+ } catch (IOException e) {
+ exception = e;
+ len = -1;
+ break;
+ }
+ }
+ return exception;
+
+ }
+
+
+ /**
+ * Copy the contents of the specified input stream to the specified
+ * output stream, and ensure that both streams are closed before returning
+ * (even in the face of an exception).
+ *
+ * @param istream The input stream to read from
+ * @param ostream The output stream to write to
+ * @param start Start of the range which will be copied
+ * @param end End of the range which will be copied
+ * @return Exception which occurred during processing
+ */
+ public static IOException copyRange(InputStream istream,
+ MessageWriter ostream,
+ long start, long end) {
+
+ try {
+ istream.skip(start);
+ } catch (IOException e) {
+ return e;
+ }
+
+ IOException exception = null;
+ long bytesToRead = end - start + 1;
+
+ byte buffer[] = new byte[input];
+ int len = buffer.length;
+ while ( (bytesToRead > 0) && (len >= buffer.length)) {
+ try {
+ len = istream.read(buffer);
+ if (bytesToRead >= len) {
+ ostream.write(buffer, 0, len);
+ bytesToRead -= len;
+ } else {
+ ostream.write(buffer, 0, (int) bytesToRead);
+ bytesToRead = 0;
+ }
+ } catch (IOException e) {
+ exception = e;
+ len = -1;
+ }
+ if (len < buffer.length)
+ break;
+ }
+
+ return exception;
+
+ }
+
+ /**
+ * Copy the contents of the specified input stream to the specified
+ * output stream, and ensure that both streams are closed before returning
+ * (even in the face of an exception).
+ *
+ * @param istream The input stream to read from
+ * @param ostream The output stream to write to
+ * @param start Start of the range which will be copied
+ * @param end End of the range which will be copied
+ * @return Exception which occurred during processing
+ */
+ public static IOException copy(InputStream istream,
+ MessageWriter ostream) {
+
+ IOException exception = null;
+ byte buffer[] = new byte[input];
+ int len = buffer.length;
+ while (true) {
+ try {
+ len = istream.read(buffer);
+ if (len < 0) {
+ break;
+ }
+ ostream.write(buffer, 0, len);
+ } catch (IOException e) {
+ exception = e;
+ len = -1;
+ break;
+ }
+ }
+
+ return exception;
+
+ }
+
+
+
+ /**
+ * Copy the contents of the specified input stream to the specified
+ * output stream, and ensure that both streams are closed before returning
+ * (even in the face of an exception).
+ *
+ * @param reader The reader to read from
+ * @param writer The writer to write to
+ * @param start Start of the range which will be copied
+ * @param end End of the range which will be copied
+ * @return Exception which occurred during processing
+ */
+ public static IOException copyRange(Reader reader, PrintWriter writer,
+ long start, long end) {
+
+ try {
+ reader.skip(start);
+ } catch (IOException e) {
+ return e;
+ }
+
+ IOException exception = null;
+ long bytesToRead = end - start + 1;
+
+ char buffer[] = new char[input];
+ int len = buffer.length;
+ while ( (bytesToRead > 0) && (len >= buffer.length)) {
+ try {
+ len = reader.read(buffer);
+ if (bytesToRead >= len) {
+ writer.write(buffer, 0, len);
+ bytesToRead -= len;
+ } else {
+ writer.write(buffer, 0, (int) bytesToRead);
+ bytesToRead = 0;
+ }
+ } catch (IOException e) {
+ exception = e;
+ len = -1;
+ }
+ if (len < buffer.length)
+ break;
+ }
+
+ return exception;
+
+ }
+
+}
Propchange: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/CopyUtils.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/Dir2Html.java
URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/Dir2Html.java?rev=690458&view=auto
==============================================================================
--- tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/Dir2Html.java (added)
+++ tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/Dir2Html.java Fri Aug 29 21:25:51 2008
@@ -0,0 +1,462 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.coyote.adapters.dav;
+
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+
+import org.apache.coyote.Request;
+import org.apache.coyote.Response;
+import org.apache.coyote.adapters.CoyoteUtils;
+import org.apache.coyote.adapters.MessageWriter;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Handles directory listing
+ */
+public class Dir2Html {
+
+ /**
+ * Array containing the safe characters set.
+ */
+ protected static URLEncoder urlEncoder;
+
+ /**
+ * Allow a readme file to be included.
+ */
+ protected String readmeFile = null;
+
+ // TODO: find a better default
+ /**
+ * The input buffer size to use when serving resources.
+ */
+ protected int input = 2048;
+
+ /**
+ * The output buffer size to use when serving resources.
+ */
+ protected int output = 2048;
+
+ /**
+ * File encoding to be used when reading static files. If none is specified
+ * the platform default is used.
+ */
+ protected String fileEncoding = null;
+
+ ThreadLocal formatTL = new ThreadLocal();
+
+ /**
+ * Full range marker.
+ */
+ protected static ArrayList FULL = new ArrayList();
+
+ // Context base dir
+ protected File basePath;
+ protected String basePathName;
+
+ public static final String TOMCAT_CSS =
+ "H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} " +
+ "H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} " +
+ "H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} " +
+ "BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} " +
+ "B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} " +
+ "P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}" +
+ "A {color : black;}" +
+ "A.name {color : black;}" +
+ "HR {color : #525D76;}";
+
+ // ----------------------------------------------------- Static Initializer
+
+
+ /**
+ * GMT timezone - all HTTP dates are on GMT
+ */
+ static {
+ urlEncoder = new URLEncoder();
+ urlEncoder.addSafeCharacter('-');
+ urlEncoder.addSafeCharacter('_');
+ urlEncoder.addSafeCharacter('.');
+ urlEncoder.addSafeCharacter('*');
+ urlEncoder.addSafeCharacter('/');
+ }
+
+
+ /**
+ * MIME multipart separation string
+ */
+ protected static final String mimeSeparation = "TOMCAT_MIME_BOUNDARY";
+
+ /**
+ * The string manager for this package.
+ */
+ protected static StringManager sm =
+ StringManager.getManager("org.apache.tomcat.servlets.file");
+
+
+
+ // --------------------------------------------------------- Public Methods
+
+
+ /**
+ * Finalize this servlet.
+ */
+ public void destroy() {
+ }
+
+
+ /**
+ * Serve the specified resource, optionally including the data content.
+ *
+ * @param request The servlet request we are processing
+ * @param response The servlet response we are creating
+ * @param content Should the content be included?
+ *
+ * @exception IOException if an input/output error occurs
+ * @exception ServletException if a servlet-specified error occurs
+ */
+ protected void serveResource(Request request,
+ Response response,
+ boolean content,
+ String relativePath)
+ throws IOException {
+
+ // Identify the requested resource path - checks include attributes
+ String path = FileAdapter.getRelativePath(request);
+
+ // TODO: Check the file cache to avoid a bunch of FS accesses.
+
+ File resFile = new File(basePath, path);
+
+ if (!resFile.exists()) {
+ FileAdapter.send404(request, response);
+ return;
+ }
+
+ boolean isDir = resFile.isDirectory();
+
+ if (isDir) {
+ renderDir(request, response, resFile,"UTF=8", content,
+ relativePath);
+ return;
+ }
+
+ }
+
+
+
+ // ----------------- Directory rendering --------------------
+
+ // Just basic HTML rendering - extend or replace for xslt
+
+ public void renderDir(Request request, Response res,
+ File resFile,
+ String fileEncoding,
+ boolean content,
+ String relativePath) throws IOException {
+
+ String contentType = "text/html;charset=" + fileEncoding;
+
+ MessageWriter writer = MessageWriter.getWriter(request, res, 0);
+
+ if (content) {
+ // Trying to retrieve the servlet output stream
+// try {
+// ostream = response.getOutputStream();
+// } catch (IllegalStateException e) {
+// // If it fails, we try to get a Writer instead if we're
+// // trying to serve a text file
+// if ( (contentType == null)
+// || (contentType.startsWith("text")) ) {
+// writer = response.getWriter();
+// } else {
+// throw e;
+// }
+// }
+
+ }
+
+ // Set the appropriate output headers
+ res.setContentType(contentType);
+
+ InputStream renderResult = null;
+
+ if (content) {
+ // Serve the directory browser
+ renderResult =
+ render(CoyoteUtils.getContextPath(request), resFile, relativePath);
+ }
+
+
+ // Copy the input stream to our output stream (if requested)
+ if (content) {
+// try {
+// response.setBufferSize(output);
+// } catch (IllegalStateException e) {
+// // Silent catch
+// }
+// if (ostream != null) {
+// CopyUtils.copy(renderResult, ostream);
+// } else {
+ CopyUtils.copy(renderResult, writer);
+// }
+ }
+
+
+ }
+
+
+ /**
+ * URL rewriter.
+ *
+ * @param path Path which has to be rewiten
+ */
+ protected String rewriteUrl(String path) {
+ return urlEncoder.encode( path );
+ }
+
+
+
+ /**
+ * Decide which way to render. HTML or XML.
+ */
+ protected InputStream render(String contextPath, File cacheEntry,
+ String relativePath) {
+ return renderHtml(contextPath, cacheEntry, relativePath);
+ }
+
+
+ /**
+ * Return an InputStream to an HTML representation of the contents
+ * of this directory.
+ *
+ * @param contextPath Context path to which our internal paths are
+ * relative
+ */
+ protected InputStream renderHtml(String contextPath, File cacheEntry,
+ String relativePath) {
+
+ String dirName = cacheEntry.getName();
+
+ // Number of characters to trim from the beginnings of filenames
+// int trim = relativePath.length();
+// if (!relativePath.endsWith("/"))
+// trim += 1;
+// if (relativePAth.equals("/"))
+// trim = 1;
+
+ // Prepare a writer to a buffered area
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ OutputStreamWriter osWriter = null;
+ try {
+ osWriter = new OutputStreamWriter(stream, "UTF8");
+ } catch (Exception e) {
+ // Should never happen
+ osWriter = new OutputStreamWriter(stream);
+ }
+ PrintWriter writer = new PrintWriter(osWriter);
+
+ StringBuffer sb = new StringBuffer();
+
+ // rewriteUrl(contextPath) is expensive. cache result for later reuse
+ String rewrittenContextPath = rewriteUrl(contextPath);
+
+ // Render the page header
+ sb.append("<html>\r\n");
+ sb.append("<head>\r\n");
+ sb.append("<title>");
+ sb.append(sm.getString("directory.title", dirName));
+ sb.append("</title>\r\n");
+ sb.append("<STYLE><!--");
+ sb.append(TOMCAT_CSS);
+ sb.append("--></STYLE> ");
+ sb.append("</head>\r\n");
+ sb.append("<body>");
+ sb.append("<h1>");
+ sb.append(sm.getString("directory.title", dirName));
+ sb.append("</h1>");
+
+ sb.append("<HR size=\"1\" noshade=\"noshade\">");
+
+
+ sb.append("<table width=\"100%\" cellspacing=\"0\"" +
+ " cellpadding=\"5\" align=\"center\">\r\n");
+
+ // Render the column headings
+ sb.append("<tr>\r\n");
+ sb.append("<td align=\"left\"><font size=\"+1\"><strong>");
+ sb.append(sm.getString("directory.filename"));
+ sb.append("</strong></font></td>\r\n");
+ sb.append("<td align=\"center\"><font size=\"+1\"><strong>");
+ sb.append(sm.getString("directory.size"));
+ sb.append("</strong></font></td>\r\n");
+ sb.append("<td align=\"right\"><font size=\"+1\"><strong>");
+ sb.append(sm.getString("directory.lastModified"));
+ sb.append("</strong></font></td>\r\n");
+ sb.append("</tr>");
+ boolean shade = false;
+
+ // Render the link to our parent (if required)
+ String parentDirectory = relativePath;
+ if (parentDirectory.endsWith("/")) {
+ parentDirectory =
+ parentDirectory.substring(0, parentDirectory.length() - 1);
+ }
+ int slash = parentDirectory.lastIndexOf('/');
+ if (slash >= 0) {
+ String parent = relativePath.substring(0, slash);
+ sb.append("<tr>\r\n<td align=\"left\"> \r\n<a href=\"..");
+// sb.append(rewrittenContextPath);
+// if (parent.equals(""))
+// parent = "/";
+// sb.append(rewriteUrl(parent));
+// if (!parent.endsWith("/"))
+// sb.append("/");
+ sb.append("\">");
+ //sb.append("<b>");
+ sb.append("..");
+ //sb.append("</b>");
+ sb.append("</a></td></tr>");
+ shade = true;
+ }
+
+
+ // Render the directory entries within this directory
+ String[] files = cacheEntry.list();
+ for (int i=0; i<files.length; i++) {
+
+ String resourceName = files[i];
+ String trimmed = resourceName;//.substring(trim);
+ if (trimmed.equalsIgnoreCase("WEB-INF") ||
+ trimmed.equalsIgnoreCase("META-INF"))
+ continue;
+
+ File childCacheEntry = new File(cacheEntry, resourceName);
+
+ sb.append("<tr");
+ if (shade)
+ sb.append(" bgcolor=\"#eeeeee\"");
+ sb.append(">\r\n");
+ shade = !shade;
+
+ sb.append("<td align=\"left\"> \r\n");
+ sb.append("<a href=\"");
+ if (! relativePath.endsWith("/")) {
+ sb.append(dirName + "/");
+ }
+ //sb.append(rewrittenContextPath);
+// if (! rewrittenContextPath.endsWith("/")) {
+// sb.append("/");
+// }
+// if ( ! relativePath.equals("")) {
+// String link = rewriteUrl(relativePath);
+// sb.append(link).append("/");
+// }
+ sb.append(resourceName);
+ boolean isDir = childCacheEntry.isDirectory();
+ if (isDir)
+ sb.append("/");
+ sb.append("\"><tt>");
+ sb.append(trimmed);
+ if (isDir)
+ sb.append("/");
+ sb.append("</tt></a></td>\r\n");
+
+ sb.append("<td align=\"right\"><tt>");
+ if (isDir)
+ sb.append(" ");
+ else
+ displaySize(sb,childCacheEntry.length());
+ sb.append("</tt></td>\r\n");
+
+ sb.append("<td align=\"right\"><tt>");
+ sb.append(FileAdapter.lastModifiedHttp(childCacheEntry));
+ sb.append("</tt></td>\r\n");
+
+ sb.append("</tr>\r\n");
+ }
+
+
+ // Render the page footer
+ sb.append("</table>\r\n");
+
+ sb.append("<HR size=\"1\" noshade=\"noshade\">");
+
+ String readme = getReadme(cacheEntry);
+ if (readme!=null) {
+ sb.append(readme);
+ sb.append("<HR size=\"1\" noshade=\"noshade\">");
+ }
+
+ sb.append("</body>\r\n");
+ sb.append("</html>\r\n");
+
+ // Return an input stream to the underlying bytes
+ writer.write(sb.toString());
+ writer.flush();
+ return (new ByteArrayInputStream(stream.toByteArray()));
+
+ }
+
+ /**
+ * Display the size of a file.
+ */
+ protected void displaySize(StringBuffer buf, long filesize) {
+
+ long leftside = filesize / 1024;
+ long rightside = (filesize % 1024) / 103; // makes 1 digit
+ if (leftside == 0 && rightside == 0 && filesize != 0)
+ rightside = 1;
+ buf.append(leftside).append(".").append(rightside);
+ buf.append(" KB");
+ }
+
+ /**
+ * Get the readme file as a string.
+ */
+ protected String getReadme(File directory) {
+ if (readmeFile!=null) {
+ try {
+ File rf = new File(directory, readmeFile);
+
+ if (rf.exists()) {
+ StringWriter buffer = new StringWriter();
+ InputStream is = new FileInputStream(rf);
+ CopyUtils.copyRange(new InputStreamReader(is),
+ new PrintWriter(buffer));
+
+ return buffer.toString();
+ }
+ } catch(Throwable e) {
+ ; /* Should only be IOException or NamingException
+ * can be ignored
+ */
+ }
+ }
+ return null;
+ }
+
+}
Propchange: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/Dir2Html.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/FileAdapter.java
URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/FileAdapter.java?rev=690458&view=auto
==============================================================================
--- tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/FileAdapter.java (added)
+++ tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/FileAdapter.java Fri Aug 29 21:25:51 2008
@@ -0,0 +1,1023 @@
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.coyote.adapters.dav;
+
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+
+
+import org.apache.coyote.Adapter;
+import org.apache.coyote.Request;
+import org.apache.coyote.Response;
+import org.apache.coyote.adapters.MessageWriter;
+import org.apache.tomcat.util.http.FastHttpDateFormat;
+import org.apache.tomcat.util.http.HttpStatus;
+import org.apache.tomcat.util.http.MimeMap;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * The default resource-serving servlet for most web applications,
+ * used to serve static resources such as HTML pages and images.
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @author Costin Manolache
+ */
+public class FileAdapter implements Adapter {
+
+
+ // ----------------------------------------------------- Instance Variables
+
+
+ /**
+ * Should we generate directory listings?
+ */
+ protected boolean listings = true;
+
+ /**
+ * Array containing the safe characters set.
+ */
+ protected static URLEncoder urlEncoder;
+
+ /**
+ * Allow a readme file to be included.
+ */
+ protected String readmeFile = null;
+
+ // TODO: find a better default
+ /**
+ * The input buffer size to use when serving resources.
+ */
+ protected static int input = 2048;
+
+ /**
+ * The output buffer size to use when serving resources.
+ */
+ protected int output = 2048;
+
+ /**
+ * File encoding to be used when reading static files. If none is specified
+ * the platform default is used.
+ */
+ protected String fileEncoding = null;
+
+ static ThreadLocal formatTL = new ThreadLocal();
+
+ Dir2Html dir2Html = new Dir2Html();
+ /**
+ * Full range marker.
+ */
+ protected static ArrayList FULL = new ArrayList();
+
+ // Context base dir
+ protected File basePath;
+ protected String basePathName;
+
+ // ----------------------------------------------------- Static Initializer
+
+
+ /**
+ * GMT timezone - all HTTP dates are on GMT
+ */
+ static {
+ urlEncoder = new URLEncoder();
+ urlEncoder.addSafeCharacter('-');
+ urlEncoder.addSafeCharacter('_');
+ urlEncoder.addSafeCharacter('.');
+ urlEncoder.addSafeCharacter('*');
+ urlEncoder.addSafeCharacter('/');
+ }
+
+
+ /**
+ * MIME multipart separation string
+ */
+ protected static final String mimeSeparation = "TOMCAT_MIME_BOUNDARY";
+
+ /**
+ * The string manager for this package.
+ */
+ protected static StringManager sm =
+ StringManager.getManager("org.apache.tomcat.servlets.file");
+
+
+
+ // --------------------------------------------------------- Public Methods
+
+
+ /**
+ * Finalize this servlet.
+ */
+ public void destroy() {
+ }
+
+ public void init(String realPath, boolean list) {
+ this.basePath = new File(realPath);
+ try {
+ basePathName = basePath.getCanonicalPath();
+ } catch (IOException e) {
+ basePathName = basePath.getAbsolutePath();
+ }
+
+ this.listings = list;
+ }
+
+ public void loadDefaultMime() throws IOException {
+ File mimeF = new File("/etc/mime.types");
+ boolean loaded =false;
+ if (!mimeF.exists()) {
+ loaded =true;
+ loadMimeFile( new FileInputStream(mimeF));
+ }
+ mimeF = new File("/etc/httpd/mime.types");
+ if (mimeF.exists()) {
+ loaded =true;
+ loadMimeFile( new FileInputStream(mimeF));
+ }
+ if (!loaded) {
+ throw new IOException("mime.types not found");
+ }
+ }
+
+ public void loadMimeFile(InputStream is) throws IOException {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+ String line = null;
+ while((line = reader.readLine()) != null) {
+ line = line.trim();
+ if (line.length() == 0) continue;
+ if (line.startsWith("#")) continue;
+ String[] parts = line.split("\\w+");
+ String type = parts[0];
+ for (int i=1; i < parts.length; i++) {
+ String ext = parts[i];
+ if (!ext.equals("")) {
+ addMimeType(type, ext);
+ System.err.println(type + " = " + ext);
+ } else {
+ System.err.println("XXX " + ext);
+ }
+ }
+ }
+
+ }
+
+ private void addMimeType(String type, String ext) {
+
+ }
+ // ------------------------------------------------------ Protected Methods
+
+ public static final String INCLUDE_REQUEST_URI_ATTR =
+ "javax.servlet.include.request_uri";
+ public static final String INCLUDE_SERVLET_PATH_ATTR =
+ "javax.servlet.include.servlet_path";
+ public static final String INCLUDE_PATH_INFO_ATTR =
+ "javax.servlet.include.path_info";
+ public static final String INCLUDE_CONTEXT_PATH_ATTR =
+ "javax.servlet.include.context_path";
+
+
+ /**
+ * Return the relative path associated with this servlet.
+ * Multiple sources are used - include attribute, servlet path, etc
+ *
+ * @param request The servlet request we are processing
+ */
+ public static String getRelativePath(Request request) {
+
+ // Are we being processed by a RequestDispatcher.include()?
+ if (request.getAttribute(INCLUDE_REQUEST_URI_ATTR) != null) {
+ String result = (String) request.getAttribute(
+ INCLUDE_PATH_INFO_ATTR);
+ if (result == null)
+ result = (String) request.getAttribute(
+ INCLUDE_SERVLET_PATH_ATTR);
+ if ((result == null) || (result.equals("")))
+ result = "/";
+ return (result);
+ }
+
+ // No, extract the desired path directly from the request
+ // For 'default' servlet, the path info contains the path
+ // if this is mapped to serve a subset of the files - we
+ // need both
+
+ String result = request.decodedURI().toString();
+ // TODO: remove context path
+
+ return result;
+ }
+
+
+ /**
+ * Check if the conditions specified in the optional If headers are
+ * satisfied.
+ *
+ * @param request The servlet request we are processing
+ * @param response The servlet response we are creating
+ * @param resourceAttributes The resource information
+ * @return boolean true if the resource meets all the specified conditions,
+ * and false if any of the conditions is not satisfied, in which case
+ * request processing is stopped
+ */
+ protected boolean checkIfHeaders(Request request,
+ Response response,
+ File resourceAttributes)
+ throws IOException {
+
+ return checkIfMatch(request, response, resourceAttributes)
+ && checkIfModifiedSince(request, response, resourceAttributes)
+ && checkIfNoneMatch(request, response, resourceAttributes)
+ && checkIfUnmodifiedSince(request, response, resourceAttributes);
+
+ }
+
+
+ /**
+ * Get the ETag associated with a file.
+ *
+ * @param resourceAttributes The resource information
+ */
+ protected String getETag(File resourceAttributes) {
+ return "W/\"" + resourceAttributes.length() + "-"
+ + resourceAttributes.lastModified() + "\"";
+ }
+
+ LRUFileCache fileCache;
+
+ public void setFileCacheSize(int size) {
+ if (fileCache == null) {
+ fileCache = new LRUFileCache();
+ }
+ fileCache.cacheSize = size;
+ }
+
+ public int getFileCacheSize() {
+ if (fileCache ==null ) return 0;
+ return fileCache.cacheSize;
+ }
+
+ static class LRUFileCache extends LinkedHashMap {
+ int cacheSize;
+ public LRUFileCache() {
+ }
+ protected boolean removeEldestEntity(Map.Entry eldest) {
+ return size() > cacheSize;
+ }
+ }
+
+ protected MimeMap mimeType = new MimeMap();
+
+ /**
+ * Serve the specified resource, optionally including the data content.
+ *
+ * @param request The servlet request we are processing
+ * @param response The servlet response we are creating
+ * @param content Should the content be included?
+ *
+ * @exception IOException if an input/output error occurs
+ * @exception ServletException if a servlet-specified error occurs
+ */
+ protected void serveResource(Request request,
+ Response response,
+ boolean content)
+ throws IOException {
+
+ // Identify the requested resource path - checks include attributes
+ String path = getRelativePath(request);
+
+ // TODO: Check the file cache to avoid a bunch of FS accesses.
+
+ File resFile = new File(basePath, path);
+
+ if (!resFile.exists()) {
+ send404(request, response);
+ return;
+ }
+
+ boolean isDir = resFile.isDirectory();
+
+ if (isDir) {
+ //getServletContext();
+ // Skip directory listings if we have been configured to
+ // suppress them
+ if (!listings) {
+ response.setStatus(404);
+ response.setMessage(request.decodedURI().toString());
+ return;
+ }
+ dir2Html.renderDir(request, response, resFile, fileEncoding, content,
+ path);
+
+ return;
+ }
+
+ // If the resource is not a collection, and the resource path
+ // ends with "/" or "\", return NOT FOUND
+ if (path.endsWith("/") || (path.endsWith("\\"))) {
+ // Check if we're included so we can return the appropriate
+ // missing resource name in the error
+ String requestUri = (String) request.getAttribute(
+ INCLUDE_REQUEST_URI_ATTR);
+ if (requestUri == null) {
+ requestUri = request.decodedURI().toString();
+ }
+ response.setStatus(404);
+ response.setMessage(requestUri);
+ return;
+ }
+
+ // Check if the conditions specified in the optional If headers are
+ // satisfied.
+
+ // Checking If headers. The method will generate the
+ boolean included =
+ (request.getAttribute(INCLUDE_CONTEXT_PATH_ATTR) != null);
+ if (!included
+ && !checkIfHeaders(request, response, resFile)) {
+ return;
+ }
+
+
+ // Find content type.
+ String contentType = mimeType.getMimeType(resFile.getName());
+
+ long contentLength = -1L;
+
+ // ETag header
+ response.setHeader("ETag", getETag(resFile));
+
+ // TODO: remove the sync, optimize - it's from ResourceAttribute
+ String lastModifiedHttp = lastModifiedHttp(resFile);
+
+ // Last-Modified header
+ response.setHeader("Last-Modified", lastModifiedHttp);
+
+ // Get content length
+ contentLength = resFile.length();
+ // Special case for zero length files, which would cause a
+ // (silent) ISE when setting the output buffer size
+ if (contentLength == 0L) {
+ content = false;
+ }
+
+ // ServletOutputStream ostream = null;
+ MessageWriter writer = MessageWriter.getWriter(request, response, 0);
+
+ if (content) {
+
+ // Trying to retrieve the servlet output stream
+
+// try {
+// ostream = response.getOutputStream();
+// } catch (IllegalStateException e) {
+// // If it fails, we try to get a Writer instead if we're
+// // trying to serve a text file
+// if ( (contentType == null)
+// || (contentType.startsWith("text")) ) {
+// writer = response.getWriter();
+// } else {
+// throw e;
+// }
+// }
+ }
+
+ // Parse range specifier
+
+ ArrayList ranges = parseRange(request, response, resFile);
+
+ if ( ( ((ranges == null) || (ranges.isEmpty()))
+ && (request.getHeader("Range") == null) )
+ || (ranges == FULL) ) {
+
+ processFullFile(response, content, resFile, contentType,
+ contentLength, writer);
+
+ } else {
+
+ if ((ranges == null) || (ranges.isEmpty()))
+ return;
+
+ // Partial content response.
+
+ response.setStatus(HttpStatus.SC_PARTIAL_CONTENT);
+ if (ranges.size() == 1) {
+
+ processSingleRange(response, content, resFile, contentType,
+ writer, ranges);
+
+ } else {
+
+ processMultiRange(response, content, resFile, contentType,
+ writer, ranges);
+
+ }
+
+ }
+
+ }
+
+
+ private void processMultiRange(Response response,
+ boolean content,
+ File resFile,
+ String contentType,
+ MessageWriter writer,
+ ArrayList ranges) throws IOException {
+ response.setContentType("multipart/byteranges; boundary="
+ + mimeSeparation);
+
+ if (content) {
+// try {
+// response.setBufferSize(output);
+// } catch (IllegalStateException e) {
+// // Silent catch
+// }
+// if (ostream != null) {
+// copyRanges(resFile, ostream, ranges.iterator(),
+// contentType);
+// } else {
+ copyRanges(resFile, writer, ranges.iterator(),
+ contentType);
+// }
+ }
+ }
+
+
+ private void processSingleRange(Response response, boolean content,
+ File resFile, String contentType,
+ MessageWriter writer, ArrayList ranges) throws IOException {
+ Range range = (Range) ranges.get(0);
+ response.addHeader("Content-Range", "bytes "
+ + range.start
+ + "-" + range.end + "/"
+ + range.length);
+ long length = range.end - range.start + 1;
+ if (length < Integer.MAX_VALUE) {
+ response.setContentLength((int) length);
+ } else {
+ // Set the content-length as String to be able to use a long
+ response.setHeader("content-length", "" + length);
+ }
+
+ if (contentType != null) {
+ response.setContentType(contentType);
+ }
+
+ if (content) {
+// try {
+// response.setBufferSize(output);
+// } catch (IllegalStateException e) {
+// // Silent catch
+// }
+// if (ostream != null) {
+// FileCopyUtils.copy(resFile, ostream, range);
+// } else {
+ CopyUtils.copyRange(new FileInputStream(resFile), writer, range.start, range.end);
+// }
+ }
+ }
+
+
+ private void processFullFile(Response response, boolean content, File resFile, String contentType, long contentLength, MessageWriter writer) throws IOException {
+ // Set the appropriate output headers
+ if (contentType != null) {
+ response.setContentType(contentType);
+ }
+ if ((contentLength >= 0)) {
+ if (contentLength < Integer.MAX_VALUE) {
+ response.setContentLength((int) contentLength);
+ } else {
+ // Set the content-length as String to be able to use a long
+ response.setHeader("content-length", "" + contentLength);
+ }
+ }
+
+ // Copy the input stream to our output stream (if requested)
+ if (content) {
+// try {
+// response.setBufferSize(output);
+// } catch (IllegalStateException e) {
+// // Silent catch
+// }
+ CopyUtils.copy(new FileInputStream(resFile), writer);
+// if (ostream != null) {
+// FileCopyUtils.copy(resFile, ostream);
+// } else {
+// FileCopyUtils.copy(resFile, writer, fileEncoding);
+// }
+ }
+ }
+
+ public static String lastModifiedHttp(File resFile) {
+ String lastModifiedHttp = null;
+ SimpleDateFormat format = (SimpleDateFormat)formatTL.get();
+ if (format == null) {
+ format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz",
+ Locale.US);
+ formatTL.set(format);
+ }
+ lastModifiedHttp = format.format(new Date(resFile.lastModified()));
+ return lastModifiedHttp;
+ }
+
+
+ protected static void send404(Request request, Response response) throws IOException {
+ // Check if we're included so we can return the appropriate
+ // missing resource name in the error
+ String requestUri = (String) request.getAttribute(
+ INCLUDE_REQUEST_URI_ATTR);
+ if (requestUri == null) {
+ requestUri = request.requestURI().toString();
+ } else {
+ // We're included, and the response.sendError() below is going
+ // to be ignored by the resource that is including us.
+ // Therefore, the only way we can let the including resource
+ // know is by including warning message in response
+ MessageWriter.getWriter(request, response, 0).write(
+ sm.getString("defaultServlet.missingResource",
+ requestUri));
+ }
+
+ sendError(response, HttpStatus.SC_NOT_FOUND,
+ requestUri);
+ return;
+ }
+
+
+
+ /**
+ * Parse the range header.
+ *
+ * @param request The servlet request we are processing
+ * @param response The servlet response we are creating
+ * @return Vector of ranges
+ */
+ protected ArrayList parseRange(Request request,
+ Response response,
+ File resourceAttributes)
+ throws IOException {
+ // if it has an IfRange and the file is newer, retur FULL
+ ArrayList result = processIfRange(request, resourceAttributes);
+ if ( result != null ) return result;
+
+ long fileLength = resourceAttributes.length();
+ if (fileLength == 0)
+ return null;
+
+ // Retrieving the range header (if any is specified
+ String rangeHeader = request.getHeader("Range");
+
+ if (rangeHeader == null)
+ return null;
+ // bytes is the only range unit supported (and I don't see the point
+ // of adding new ones).
+ if (!rangeHeader.startsWith("bytes")) {
+ return sendRangeNotSatisfiable(response, fileLength);
+ }
+
+ rangeHeader = rangeHeader.substring(6);
+
+ // Vector which will contain all the ranges which are successfully
+ // parsed.
+ result = Range.parseRanges(fileLength, rangeHeader);
+ if (result == null) {
+ sendRangeNotSatisfiable(response, fileLength);
+ }
+ return result;
+ }
+
+
+ private ArrayList sendRangeNotSatisfiable(Response response,
+ long fileLength)
+ throws IOException {
+ response.addHeader("Content-Range", "bytes */" + fileLength);
+ sendError
+ (response, HttpStatus.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
+ return null;
+ }
+
+
+
+ private ArrayList processIfRange(Request request,
+ File resourceAttributes) {
+ // Checking If-Range
+ String headerValue = request.getHeader("If-Range");
+
+ if (headerValue != null) {
+
+ long headerValueTime = (-1L);
+ try {
+ headerValueTime = getDateHeader(request, "If-Range");
+ } catch (Exception e) {
+ ;
+ }
+
+ String eTag = getETag(resourceAttributes);
+ long lastModified = resourceAttributes.lastModified();
+
+ if (headerValueTime == (-1L)) {
+
+ // If the ETag the client gave does not match the entity
+ // etag, then the entire entity is returned.
+ if (!eTag.equals(headerValue.trim()))
+ return FULL;
+
+ } else {
+
+ // If the timestamp of the entity the client got is older than
+ // the last modification date of the entity, the entire entity
+ // is returned.
+ if (lastModified > (headerValueTime + 1000))
+ return FULL;
+
+ }
+
+ }
+ return null;
+ }
+
+
+ // -------------------------------------------------------- protected Methods
+
+
+ /**
+ * Check if the if-match condition is satisfied.
+ *
+ * @param request The servlet request we are processing
+ * @param response The servlet response we are creating
+ * @param resourceInfo File object
+ * @return boolean true if the resource meets the specified condition,
+ * and false if the condition is not satisfied, in which case request
+ * processing is stopped
+ */
+ protected boolean checkIfMatch(Request request,
+ Response response,
+ File resourceAttributes)
+ throws IOException {
+
+ String eTag = getETag(resourceAttributes);
+ String headerValue = request.getHeader("If-Match");
+ if (headerValue != null) {
+ if (headerValue.indexOf('*') == -1) {
+
+ StringTokenizer commaTokenizer = new StringTokenizer
+ (headerValue, ",");
+ boolean conditionSatisfied = false;
+
+ while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
+ String currentToken = commaTokenizer.nextToken();
+ if (currentToken.trim().equals(eTag))
+ conditionSatisfied = true;
+ }
+
+ // If none of the given ETags match, 412 Precodition failed is
+ // sent back
+ if (!conditionSatisfied) {
+ sendError
+ (response, HttpStatus.SC_PRECONDITION_FAILED);
+ return false;
+ }
+
+ }
+ }
+ return true;
+
+ }
+
+
+ /**
+ * Check if the if-modified-since condition is satisfied.
+ *
+ * @param request The servlet request we are processing
+ * @param response The servlet response we are creating
+ * @param resourceInfo File object
+ * @return boolean true if the resource meets the specified condition,
+ * and false if the condition is not satisfied, in which case request
+ * processing is stopped
+ */
+ protected boolean checkIfModifiedSince(Request request,
+ Response response,
+ File resourceAttributes)
+ throws IOException {
+ try {
+ long headerValue = getDateHeader(request, "If-Modified-Since");
+ long lastModified = resourceAttributes.lastModified();
+ if (headerValue != -1) {
+
+ // If an If-None-Match header has been specified, if modified since
+ // is ignored.
+ if ((request.getHeader("If-None-Match") == null)
+ && (lastModified <= headerValue + 1000)) {
+ // The entity has not been modified since the date
+ // specified by the client. This is not an error case.
+ response.setStatus(HttpStatus.SC_NOT_MODIFIED);
+ return false;
+ }
+ }
+ } catch(IllegalArgumentException illegalArgument) {
+ return true;
+ }
+ return true;
+
+ }
+
+
+ /**
+ * Check if the if-none-match condition is satisfied.
+ *
+ * @param request The servlet request we are processing
+ * @param response The servlet response we are creating
+ * @param resourceInfo File object
+ * @return boolean true if the resource meets the specified condition,
+ * and false if the condition is not satisfied, in which case request
+ * processing is stopped
+ */
+ protected boolean checkIfNoneMatch(Request request,
+ Response response,
+ File resourceAttributes)
+ throws IOException {
+
+ String eTag = getETag(resourceAttributes);
+ String headerValue = request.getHeader("If-None-Match");
+ if (headerValue != null) {
+
+ boolean conditionSatisfied = false;
+
+ if (!headerValue.equals("*")) {
+
+ StringTokenizer commaTokenizer =
+ new StringTokenizer(headerValue, ",");
+
+ while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
+ String currentToken = commaTokenizer.nextToken();
+ if (currentToken.trim().equals(eTag))
+ conditionSatisfied = true;
+ }
+
+ } else {
+ conditionSatisfied = true;
+ }
+
+ if (conditionSatisfied) {
+
+ // For GET and HEAD, we should respond with
+ // 304 Not Modified.
+ // For every other method, 412 Precondition Failed is sent
+ // back.
+ if ( ("GET".equals(request.method().toString()))
+ || ("HEAD".equals(request.method().toString())) ) {
+ response.setStatus(HttpStatus.SC_NOT_MODIFIED);
+ return false;
+ } else {
+ sendError
+ (response, HttpStatus.SC_PRECONDITION_FAILED);
+ return false;
+ }
+ }
+ }
+ return true;
+
+ }
+
+
+ /**
+ * Check if the if-unmodified-since condition is satisfied.
+ *
+ * @param request The servlet request we are processing
+ * @param response The servlet response we are creating
+ * @param resourceInfo File object
+ * @return boolean true if the resource meets the specified condition,
+ * and false if the condition is not satisfied, in which case request
+ * processing is stopped
+ */
+ protected boolean checkIfUnmodifiedSince(Request request,
+ Response response,
+ File resourceAttributes)
+ throws IOException {
+ try {
+ long lastModified = resourceAttributes.lastModified();
+ long headerValue = getDateHeader(request, "If-Unmodified-Since");
+ if (headerValue != -1) {
+ if ( lastModified > (headerValue + 1000)) {
+ // The entity has not been modified since the date
+ // specified by the client. This is not an error case.
+ sendError(response, HttpStatus.SC_PRECONDITION_FAILED);
+ return false;
+ }
+ }
+ } catch(IllegalArgumentException illegalArgument) {
+ return true;
+ }
+ return true;
+
+ }
+
+
+
+ /**
+ * Copy the contents of the specified input stream to the specified
+ * output stream, and ensure that both streams are closed before returning
+ * (even in the face of an exception).
+ *
+ * @param resourceInfo The ResourceInfo object
+ * @param ostream The output stream to write to
+ * @param ranges Enumeration of the ranges the client wanted to retrieve
+ * @param contentType Content type of the resource
+ * @exception IOException if an input/output error occurs
+ */
+ protected void copyRanges(File cacheEntry, MessageWriter ostream,
+ Iterator ranges, String contentType)
+ throws IOException {
+
+ IOException exception = null;
+
+ while ( (exception == null) && (ranges.hasNext()) ) {
+
+ InputStream resourceInputStream = new FileInputStream(cacheEntry);
+ InputStream istream =
+ new BufferedInputStream(resourceInputStream, input);
+
+ Range currentRange = (Range) ranges.next();
+
+ // Writing MIME header.
+ ostream.println();
+ ostream.println("--" + mimeSeparation);
+ if (contentType != null)
+ ostream.println("Content-Type: " + contentType);
+ ostream.println("Content-Range: bytes " + currentRange.start
+ + "-" + currentRange.end + "/"
+ + currentRange.length);
+ ostream.println();
+
+ // Printing content
+ exception = CopyUtils.copyRange(istream, ostream, currentRange.start,
+ currentRange.end);
+
+ try {
+ istream.close();
+ } catch (Throwable t) {
+ ;
+ }
+
+ }
+
+ ostream.println();
+ ostream.print("--" + mimeSeparation + "--");
+
+ // Rethrow any exception that has occurred
+ if (exception != null)
+ throw exception;
+
+ }
+
+ protected SimpleDateFormat formats[] = null;
+ protected static final TimeZone GMT_ZONE = TimeZone.getTimeZone("GMT");
+
+ public long getDateHeader(Request req, String name) {
+
+ String value = req.getHeader(name);
+ if (value == null)
+ return (-1L);
+ if (formats == null) {
+ formats = new SimpleDateFormat[] {
+ new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
+ new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
+ new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)
+ };
+ formats[0].setTimeZone(GMT_ZONE);
+ formats[1].setTimeZone(GMT_ZONE);
+ formats[2].setTimeZone(GMT_ZONE);
+ }
+
+ // Attempt to convert the date header in a variety of formats
+ long result = FastHttpDateFormat.parseDate(value, formats);
+ if (result != (-1L)) {
+ return result;
+ }
+ throw new IllegalArgumentException(value);
+
+ }
+
+ static void sendError(Response resp, int status, String msg) {
+ resp.setStatus(status);
+ resp.setMessage(msg);
+ }
+
+ void sendError(Response resp, int status) {
+ resp.setStatus(status);
+ }
+
+ /**
+ * Copy the contents of the specified input stream to the specified
+ * output stream, and ensure that both streams are closed before returning
+ * (even in the face of an exception).
+ *
+ * @param resourceInfo The ResourceInfo object
+ * @param writer The writer to write to
+ * @param ranges Enumeration of the ranges the client wanted to retrieve
+ * @param contentType Content type of the resource
+ * @exception IOException if an input/output error occurs
+ */
+ protected void copyRanges(File cacheEntry, PrintWriter writer,
+ Iterator ranges, String contentType)
+ throws IOException {
+
+ IOException exception = null;
+
+ // quite inefficient - why not sort and open once
+ while ( (exception == null) && (ranges.hasNext()) ) {
+
+ InputStream resourceInputStream = new FileInputStream(cacheEntry);
+
+ Reader reader;
+ if (fileEncoding == null) {
+ reader = new InputStreamReader(resourceInputStream);
+ } else {
+ reader = new InputStreamReader(resourceInputStream,
+ fileEncoding);
+ }
+
+ Range currentRange = (Range) ranges.next();
+
+ // Writing MIME header.
+ writer.println();
+ writer.println("--" + mimeSeparation);
+ if (contentType != null)
+ writer.println("Content-Type: " + contentType);
+ writer.println("Content-Range: bytes " + currentRange.start
+ + "-" + currentRange.end + "/"
+ + currentRange.length);
+ writer.println();
+
+ // Printing content
+ exception = CopyUtils.copyRange(reader, writer, currentRange.start,
+ currentRange.end);
+
+ try {
+ reader.close();
+ } catch (Throwable t) {
+ ;
+ }
+
+ }
+ writer.println();
+ writer.print("--" + mimeSeparation + "--");
+
+ // Rethrow any exception that has occurred
+ if (exception != null)
+ throw exception;
+ }
+
+
+ public boolean event(Request req, Response res, SocketStatus status)
+ throws Exception {
+ return false;
+ }
+
+
+ public void service(Request req, Response res) throws IOException {
+ String method = req.method().toString();
+ if ("GET".equals(method)) {
+ serveResource(req, res, true);
+ } else if ("HEAD".equals(method)) {
+ serveResource(req, res, false);
+ } else {
+ serveResource(req, res, true);
+ }
+ }
+
+}
Propchange: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/FileAdapter.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/LocalStrings.properties
URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/LocalStrings.properties?rev=690458&view=auto
==============================================================================
--- tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/LocalStrings.properties (added)
+++ tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/LocalStrings.properties Fri Aug 29 21:25:51 2008
@@ -0,0 +1,19 @@
+defaultServlet.missingResource=The requested resource ({0}) is not available
+defaultservlet.directorylistingfor=Directory Listing for:
+defaultservlet.upto=Up to:
+defaultservlet.subdirectories=Subdirectories:
+defaultservlet.files=Files:
+invokerServlet.allocate=Cannot allocate servlet instance for path {0}
+invokerServlet.cannotCreate=Cannot create servlet wrapper for path {0}
+invokerServlet.deallocate=Cannot deallocate servlet instance for path {0}
+invokerServlet.invalidPath=No servlet name or class was specified in path {0}
+invokerServlet.notNamed=Cannot call invoker servlet with a named dispatcher
+invokerServlet.noWrapper=Container has not called setWrapper() for this servlet
+webdavservlet.jaxpfailed=JAXP initialization failed
+directory.filename=Filename
+directory.lastModified=Last Modified
+directory.parent=Up To {0}
+directory.size=Size
+directory.title=Directory Listing For {0}
+directory.version=Tomcat Catalina version 4.0
+
Propchange: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/LocalStrings.properties
------------------------------------------------------------------------------
svn:eol-style = native
Added: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/Range.java
URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/Range.java?rev=690458&view=auto
==============================================================================
--- tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/Range.java (added)
+++ tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/Range.java Fri Aug 29 21:25:51 2008
@@ -0,0 +1,146 @@
+/**
+ *
+ */
+package org.apache.coyote.adapters.dav;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+
+/**
+ * Utils to process HTTP/1.1 ranges. Used by default servlet, could
+ * be used by any servlet that needs to deal with ranges.
+ *
+ * It is very good to support ranges if you have large content. In most
+ * cases supporting one range is enough - getting multiple ranges doesn't
+ * seem very common, and it's complex (multipart response).
+ *
+ * @author Costin Manolache
+ * @author Remy Maucherat
+ * @author - see DefaultServlet in Catalin for other contributors
+ */
+public class Range {
+
+ public long start;
+ public long end;
+ public long length;
+
+ /**
+ * Validate range.
+ */
+ public boolean validate() {
+ if (end >= length)
+ end = length - 1;
+ return ( (start >= 0) && (end >= 0) && (start <= end)
+ && (length > 0) );
+ }
+
+ public void recycle() {
+ start = 0;
+ end = 0;
+ length = 0;
+ }
+
+ /** Parse ranges.
+ *
+ * @return null if the range is invalid or can't be parsed
+ */
+ public static ArrayList parseRanges(long fileLength,
+ String rangeHeader) throws IOException {
+ ArrayList result = new ArrayList();
+ StringTokenizer commaTokenizer = new StringTokenizer(rangeHeader, ",");
+
+ // Parsing the range list
+ while (commaTokenizer.hasMoreTokens()) {
+ String rangeDefinition = commaTokenizer.nextToken().trim();
+
+ Range currentRange = new Range();
+ currentRange.length = fileLength;
+
+ int dashPos = rangeDefinition.indexOf('-');
+
+ if (dashPos == -1) {
+ return null;
+ }
+
+ if (dashPos == 0) {
+ try {
+ long offset = Long.parseLong(rangeDefinition);
+ currentRange.start = fileLength + offset;
+ currentRange.end = fileLength - 1;
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ } else {
+
+ try {
+ currentRange.start = Long.parseLong
+ (rangeDefinition.substring(0, dashPos));
+ if (dashPos < rangeDefinition.length() - 1)
+ currentRange.end = Long.parseLong
+ (rangeDefinition.substring
+ (dashPos + 1, rangeDefinition.length()));
+ else
+ currentRange.end = fileLength - 1;
+ } catch (NumberFormatException e) {
+ return null;
+ }
+
+ }
+ if (!currentRange.validate()) {
+ return null;
+ }
+ result.add(currentRange);
+ }
+ return result;
+ }
+
+
+ /**
+ * Parse the Content-Range header. Used with PUT or in response.
+ *
+ * @return Range
+ */
+ public static Range parseContentRange(String rangeHeader)
+ throws IOException {
+ if (rangeHeader == null)
+ return null;
+
+ // bytes is the only range unit supported
+ if (!rangeHeader.startsWith("bytes")) {
+ return null;
+ }
+
+ rangeHeader = rangeHeader.substring(6).trim();
+
+ int dashPos = rangeHeader.indexOf('-');
+ int slashPos = rangeHeader.indexOf('/');
+
+ if (dashPos == -1) {
+ return null;
+ }
+
+ if (slashPos == -1) {
+ return null;
+ }
+
+ Range range = new Range();
+
+ try {
+ range.start = Long.parseLong(rangeHeader.substring(0, dashPos));
+ range.end =
+ Long.parseLong(rangeHeader.substring(dashPos + 1, slashPos));
+ range.length = Long.parseLong
+ (rangeHeader.substring(slashPos + 1, rangeHeader.length()));
+ } catch (NumberFormatException e) {
+ return null;
+ }
+
+ if (!range.validate()) {
+ return null;
+ }
+
+ return range;
+ }
+
+}
\ No newline at end of file
Propchange: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/Range.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/URLEncoder.java
URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/URLEncoder.java?rev=690458&view=auto
==============================================================================
--- tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/URLEncoder.java (added)
+++ tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/URLEncoder.java Fri Aug 29 21:25:51 2008
@@ -0,0 +1,100 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.coyote.adapters.dav;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.BitSet;
+
+/**
+ *
+ * This class is very similar to the java.net.URLEncoder class.
+ *
+ * Unfortunately, with java.net.URLEncoder there is no way to specify to the
+ * java.net.URLEncoder which characters should NOT be encoded.
+ *
+ * This code was moved from DefaultServlet.java
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ */
+public class URLEncoder {
+ protected static final char[] hexadecimal =
+ {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'A', 'B', 'C', 'D', 'E', 'F'};
+
+ //Array containing the safe characters set.
+ protected BitSet safeCharacters = new BitSet(256);
+
+ public URLEncoder() {
+ for (char i = 'a'; i <= 'z'; i++) {
+ addSafeCharacter(i);
+ }
+ for (char i = 'A'; i <= 'Z'; i++) {
+ addSafeCharacter(i);
+ }
+ for (char i = '0'; i <= '9'; i++) {
+ addSafeCharacter(i);
+ }
+ }
+
+ public void addSafeCharacter( char c ) {
+ safeCharacters.set( c );
+ }
+
+ public String encode( String path ) {
+ int maxBytesPerChar = 10;
+ int caseDiff = ('a' - 'A');
+ StringBuffer rewrittenPath = new StringBuffer(path.length());
+ ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar);
+ OutputStreamWriter writer = null;
+ try {
+ writer = new OutputStreamWriter(buf, "UTF8");
+ } catch (Exception e) {
+ e.printStackTrace();
+ writer = new OutputStreamWriter(buf);
+ }
+
+ for (int i = 0; i < path.length(); i++) {
+ int c = (int) path.charAt(i);
+ if (safeCharacters.get(c)) {
+ rewrittenPath.append((char)c);
+ } else {
+ // convert to external encoding before hex conversion
+ try {
+ writer.write((char)c);
+ writer.flush();
+ } catch(IOException e) {
+ buf.reset();
+ continue;
+ }
+ byte[] ba = buf.toByteArray();
+ for (int j = 0; j < ba.length; j++) {
+ // Converting each byte in the buffer
+ byte toEncode = ba[j];
+ rewrittenPath.append('%');
+ int low = (int) (toEncode & 0x0f);
+ int high = (int) ((toEncode & 0xf0) >> 4);
+ rewrittenPath.append(hexadecimal[high]);
+ rewrittenPath.append(hexadecimal[low]);
+ }
+ buf.reset();
+ }
+ }
+ return rewrittenPath.toString();
+ }
+}
Propchange: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/URLEncoder.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/UrlUtils.java
URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/UrlUtils.java?rev=690458&view=auto
==============================================================================
--- tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/UrlUtils.java (added)
+++ tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/UrlUtils.java Fri Aug 29 21:25:51 2008
@@ -0,0 +1,67 @@
+package org.apache.coyote.adapters.dav;
+
+public class UrlUtils {
+
+ /** Used by webdav.
+ *
+ * Return a context-relative path, beginning with a "/", that represents
+ * the canonical version of the specified path after ".." and "." elements
+ * are resolved out. If the specified path attempts to go outside the
+ * boundaries of the current context (i.e. too many ".." path elements
+ * are present), return <code>null</code> instead.
+ *
+ * @param path Path to be normalized
+ */
+ public static String normalize(String path) {
+
+ if (path == null)
+ return null;
+
+ // Create a place for the normalized path
+ String normalized = path;
+
+ if (normalized.equals("/."))
+ return "/";
+
+ // Normalize the slashes and add leading slash if necessary
+ if (normalized.indexOf('\\') >= 0)
+ normalized = normalized.replace('\\', '/');
+
+ if (!normalized.startsWith("/"))
+ normalized = "/" + normalized;
+
+ // Resolve occurrences of "//" in the normalized path
+ while (true) {
+ int index = normalized.indexOf("//");
+ if (index < 0)
+ break;
+ normalized = normalized.substring(0, index) +
+ normalized.substring(index + 1);
+ }
+
+ // Resolve occurrences of "/./" in the normalized path
+ while (true) {
+ int index = normalized.indexOf("/./");
+ if (index < 0)
+ break;
+ normalized = normalized.substring(0, index) +
+ normalized.substring(index + 2);
+ }
+
+ // Resolve occurrences of "/../" in the normalized path
+ while (true) {
+ int index = normalized.indexOf("/../");
+ if (index < 0)
+ break;
+ if (index == 0)
+ return (null); // Trying to go outside our context
+ int index2 = normalized.lastIndexOf('/', index - 1);
+ normalized = normalized.substring(0, index2) +
+ normalized.substring(index + 3);
+ }
+
+ // Return the normalized path that we have completed
+ return (normalized);
+ }
+
+}
Propchange: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/dav/UrlUtils.java
------------------------------------------------------------------------------
svn:eol-style = native
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org