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\">&nbsp;&nbsp;\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\">&nbsp;&nbsp;\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("&nbsp;");
+            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