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...@locus.apache.org on 2000/11/03 22:27:53 UTC

cvs commit: jakarta-tomcat/src/webdav/org/apache/tomcat/webdav/util DOMWriter.java MD5Encoder.java MIME2Java.java StringManager.java XMLWriter.java

costin      00/11/03 13:27:48

  Modified:    .        build.xml
  Added:       src/webdav/org/apache/tomcat/webdav DefaultServlet.java
                        LocalStrings.properties WebdavServlet.java
               src/webdav/org/apache/tomcat/webdav/resources
                        DirectoryBean.java FileResources.java
                        JarResources.java LocalStrings.properties
                        ResourceBean.java ResourceUtils.java Resources.java
                        ResourcesBase.java
               src/webdav/org/apache/tomcat/webdav/util DOMWriter.java
                        MD5Encoder.java MIME2Java.java StringManager.java
                        XMLWriter.java
  Log:
  Initial commit for the webdav module from catalina.
  
  Most of the junk is removed, but a bit more refactoring is still needed -
  the cache generates a lot of garbage and the number of threads need to
  be reduced ( and integrated with the server's thread management )
  
  The new tomcat3 module has no external dependency ( except servlet22.jar ).
  It doesn't work yet, but compiles fine ( it's very close to working :-)
  
  Probably the resources should go to tomcat.util.resources, as we may reuse
  them ( or at least the cache ) for static files ( well, that's just for
  benchmark purpose, I don't think any decent admin would turn this on,
  using a large cache in java will kill everything else - of course,
  if only one file is cached, like in a "ab" benchmark, it looks ok ... )
  
  The only missing part is the ServerLiaison, that will allow it to plug
  into any servlet container.
  
  Revision  Changes    Path
  1.90      +19 -1     jakarta-tomcat/build.xml
  
  Index: build.xml
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/build.xml,v
  retrieving revision 1.89
  retrieving revision 1.90
  diff -u -r1.89 -r1.90
  --- build.xml	2000/11/02 21:56:58	1.89
  +++ build.xml	2000/11/03 21:27:31	1.90
  @@ -237,6 +237,24 @@
       </jar>
     </target>
   
  +  <!-- ==================== Webdav ========== -->
  +  <target name="dav" depends="init" >
  +    <javac destdir="${tomcat.build}/classes"
  +      debug="${debug}" 
  +      optimize="${optimize}"
  +      deprecation="off"
  +      srcdir="src/webdav" >
  +      <classpath>
  +	<pathelement location="${servlet22.jar}" />
  +      </classpath>
  +      <include name="org/apache/tomcat/webdav/**" />    
  +    </javac>
  +    <jar jarfile="${tomcat.build}/lib/webdav.jar"
  +      basedir="${tomcat.build}/classes" > 
  +      <include name="org/apache/tomcat/webdav/**" /> 
  +    </jar>
  +  </target>
  +
     <!-- ==================== Servlet 23 (default) implementation ========== -->
     <target name="facade23" depends="init" >
       <javac destdir="${tomcat.build}/classes"
  @@ -315,7 +333,7 @@
        />
     </target>
   
  -  <target name="tomcat-jars-new" depends="tomcat_util,tomcat.jar,tomcat_core,jasper,tomcat_modules,facade22,facade23,tomcat_config">
  +  <target name="tomcat-jars-new" depends="tomcat_util,tomcat.jar,tomcat_core,jasper,tomcat_modules,facade22,facade23,tomcat_config,dav">
     </target>
   
     <!-- ==================== J2EE integration ========== -->
  
  
  
  1.1                  jakarta-tomcat/src/webdav/org/apache/tomcat/webdav/DefaultServlet.java
  
  Index: DefaultServlet.java
  ===================================================================
  /*
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */
  
  
  package org.apache.tomcat.webdav;
  
  
  import java.io.BufferedInputStream;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.InputStream;
  import java.io.IOException;
  import java.io.PrintWriter;
  import java.io.Reader;
  import java.io.InputStreamReader;
  import java.io.Writer;
  import java.net.MalformedURLException;
  import java.net.URL;
  import java.sql.Timestamp;
  import java.util.Date;
  import java.util.Enumeration;
  import java.util.Vector;
  import java.util.StringTokenizer;
  import java.util.Locale;
  import java.util.Hashtable;
  import java.text.ParseException;
  import java.text.SimpleDateFormat;
  import java.security.MessageDigest;
  import java.security.NoSuchAlgorithmException;
  import javax.servlet.RequestDispatcher;
  import javax.servlet.ServletException;
  import javax.servlet.ServletContext;
  import javax.servlet.ServletOutputStream;
  import javax.servlet.http.HttpServlet;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  import org.apache.tomcat.webdav.util.*;
  import org.apache.tomcat.webdav.resources.*;
  // import org.apache.catalina.Globals;
  // import org.apache.catalina.Resources;
  // import org.apache.catalina.core.ApplicationContext;
  // import org.apache.catalina.resources.ResourceBean;
  // import org.apache.catalina.resources.DirectoryBean;
  // import org.apache.catalina.util.MD5Encoder;
  // import org.apache.catalina.util.StringManager;
  // import org.apache.catalina.util.xml.SaxContext;
  // import org.apache.catalina.util.xml.XmlAction;
  // import org.apache.catalina.util.xml.XmlMapper;
  
  
  /**
   * 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
   * @version $Revision: 1.1 $ $Date: 2000/11/03 21:27:37 $
   */
  
  public class DefaultServlet
      extends HttpServlet {
  
      /**
       * The servlet context attribute under which we record the set of
       * welcome files (as an object of type String[]) for this application.
       */
      public static final String WELCOME_FILES_ATTR =
  	"org.apache.catalina.WELCOME_FILES";
  
      // ----------------------------------------------------- Instance Variables
  
  
      /**
       * The debugging detail level for this servlet.
       */
      protected int debug = 0;
  
  
      /**
       * The input buffer size to use when serving resources.
       */
      protected int input = 2048;
  
  
      /**
       * Should we generate directory listings when no welcome file is present?
       */
      protected boolean listings = true;
  
  
      /**
       * Read only flag. By default, it's set to true.
       */
      protected boolean readOnly = true;
  
  
      /**
       * The output buffer size to use when serving resources.
       */
      protected int output = 2048;
  
  
      /**
       * The set of welcome files for this web application
       */
      protected String welcomes[] = new String[0];
  
  
      /**
       * MD5 message digest provider.
       */
      protected static MessageDigest md5Helper;
  
  
      /**
       * The MD5 helper object for this class.
       */
      protected static final MD5Encoder md5Encoder = new MD5Encoder();
  
  
      /**
       * The set of SimpleDateFormat formats to use in getDateHeader().
       */
      protected static final SimpleDateFormat formats[] = {
  	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)
      };
  
  
      /**
       * MIME multipart separation string
       */
      protected static final String mimeSeparation = "CATALINA_MIME_BOUNDARY";
  
  
      /**
       * The string manager for this package.
       */
      protected static StringManager sm =
  	StringManager.getManager("org.apache.tomcat.webdav");
  
  
      // --------------------------------------------------------- Public Methods
  
  
      /**
       * Finalize this servlet.
       */
      public void destroy() {
  
  	;	// No actions necessary
  
      }
  
  
      /**
       * Initialize this servlet.
       */
      public void init() throws ServletException {
  
  	// Set our properties from the initialization parameters
  	String value = null;
  	try {
  	    value = getServletConfig().getInitParameter("debug");
  	    debug = Integer.parseInt(value);
  	} catch (Throwable t) {
  	    ;
  	}
  	try {
  	    value = getServletConfig().getInitParameter("input");
  	    input = Integer.parseInt(value);
  	} catch (Throwable t) {
  	    ;
  	}
  	try {
  	    value = getServletConfig().getInitParameter("listings");
  	    listings = (new Boolean(value)).booleanValue();
  	} catch (Throwable t) {
  	    ;
  	}
  	try {
  	    value = getServletConfig().getInitParameter("readonly");
  	    readOnly = (new Boolean(value)).booleanValue();
  	} catch (Throwable t) {
  	    ;
  	}
  	try {
  	    value = getServletConfig().getInitParameter("output");
  	    output = Integer.parseInt(value);
  	} catch (Throwable t) {
  	    ;
  	}
  
  	// Sanity check on the specified buffer sizes
  	if (input < 256)
  	    input = 256;
  	if (output < 256)
  	    output = 256;
  
  	// Initialize the set of welcome files for this application
  	welcomes = (String[]) getServletContext().getAttribute
  	    (WELCOME_FILES_ATTR);
  	if (welcomes == null)
  	    welcomes = new String[0];
  
  	if (debug > 0) {
  	    log("DefaultServlet.init:  input buffer size=" + input +
  		", output buffer size=" + output);
  	    for (int i = 0; i < welcomes.length; i++)
  		log("DefaultServlet.init:  welcome file=" +
  		    welcomes[i]);
  	}
  
          // Load the MD5 helper used to calculate signatures.
          try {
              md5Helper = MessageDigest.getInstance("MD5");
          } catch (NoSuchAlgorithmException e) {
              e.printStackTrace();
              throw new IllegalStateException();
          }
  
      }
  
  
  
      // ------------------------------------------------------ Protected Methods
  
  
      /**
       * Return the relative path associated with this servlet.
       *
       * @param request The servlet request we are processing
       */
      protected String getRelativePath(HttpServletRequest request) {
          
          // Are we being processed by a RequestDispatcher.include()?
          if (request.getAttribute("javax.servlet.include.request_uri")!=null) {
              String result = (String)
                  request.getAttribute("javax.servlet.include.path_info");
              if (result == null)
                  result = (String)
                      request.getAttribute("javax.servlet.include.servlet_path");
              if ((result == null) || (result.equals("")))
                  result = "/";
              return (result);
          }
  
          // No, extract the desired path directly from the request
          String result = request.getPathInfo();
          if (result == null) {
              result = request.getServletPath();
          }
          if ((result == null) || (result.equals(""))) {
              result = "/";
          }
          return result;
          
      }
  
  
      /**
       * Process a GET request for the specified resource.
       *
       * @param request The servlet request we are processing
       * @param response The servlet response we are creating
       *
       * @exception IOException if an input/output error occurs
       * @exception ServletException if a servlet-specified error occurs
       */
      protected void doGet(HttpServletRequest request,
                           HttpServletResponse response)
  	throws IOException, ServletException {
  
  	// Serve the requested resource, including the data content
  	serveResource(request, response, true);
  
      }
  
  
      /**
       * Process a HEAD request for the specified resource.
       *
       * @param request The servlet request we are processing
       * @param response The servlet response we are creating
       *
       * @exception IOException if an input/output error occurs
       * @exception ServletException if a servlet-specified error occurs
       */
      protected void doHead(HttpServletRequest request,
                            HttpServletResponse response)
  	throws IOException, ServletException {
  
  	// Serve the requested resource, without the data content
          serveResource(request, response, false);
  
      }
  
  
      /**
       * Process a POST request for the specified resource.
       *
       * @param request The servlet request we are processing
       * @param response The servlet response we are creating
       *
       * @exception IOException if an input/output error occurs
       * @exception ServletException if a servlet-specified error occurs
       */
      protected void doPost(HttpServletRequest request,
                            HttpServletResponse response)
  	throws IOException, ServletException {
  
  	doPut(request, response);
  
      }
  
  
      protected Resources getResources() {
  	// XXX get context resources
  	return null;//new Container( getServletContext() );
      }
  
  
      /**
       * Process a POST request for the specified resource.
       *
       * @param request The servlet request we are processing
       * @param response The servlet response we are creating
       *
       * @exception IOException if an input/output error occurs
       * @exception ServletException if a servlet-specified error occurs
       */
      protected void doPut(HttpServletRequest req, HttpServletResponse resp)
  	throws ServletException, IOException {
  
          if (readOnly) {
              resp.sendError(HttpServletResponse.SC_FORBIDDEN);
              return;
          }
          
          String path = getRelativePath(req);
          
          // Looking for a Content-Range header
          if (req.getHeader("Content-Range") != null) {
              // No content range header is supported
              resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
          }
          
          Resources resources = getResources();
          
          boolean exists = resources.exists(path);
          
          boolean result = resources.setResource(path, req.getInputStream());
          
          if (result) {
              if (exists) {
                  resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
              } else {
                  resp.setStatus(HttpServletResponse.SC_CREATED);
              }
          } else {
              resp.sendError(HttpServletResponse.SC_CONFLICT);
          }
          
      }
  
  
      /**
       * Process a POST request for the specified resource.
       *
       * @param request The servlet request we are processing
       * @param response The servlet response we are creating
       *
       * @exception IOException if an input/output error occurs
       * @exception ServletException if a servlet-specified error occurs
       */
      protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
  	throws ServletException, IOException {
  
          if (readOnly) {
              resp.sendError(HttpServletResponse.SC_FORBIDDEN);
              return;
          }
          
          String path = getRelativePath(req);
          
          Resources resources = getResources();
          
          boolean exists = resources.exists(path);
          
          if (exists) {
              boolean result = resources.deleteResource(path);
              if (result) {
                  resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
              } else {
                  resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
              }
          } else {
              resp.sendError(HttpServletResponse.SC_NOT_FOUND);
          }
          
      }
  
  
      /**
       * 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 resourceInfo File object
       * @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(HttpServletRequest request,
                                       HttpServletResponse response, 
                                       ResourceInfo resourceInfo)
          throws IOException {
          
          String eTag = getETag(resourceInfo, true);
          long fileLength = resourceInfo.length;
          long lastModified = resourceInfo.date;
          
          StringTokenizer commaTokenizer;
          
          String headerValue;
          
          // Checking If-Match
          headerValue = request.getHeader("If-Match");
          if (headerValue != null) {
              if (headerValue.indexOf("*") == -1) {
                  
                  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) {
                      response.sendError
                          (HttpServletResponse.SC_PRECONDITION_FAILED);
                      return false;
                  }
                  
              }
          }
          
          // Checking If-Modified-Since
          headerValue = request.getHeader("If-Modified-Since");
          if (headerValue != null) {
              
              // If an If-None-Match header has been specified, if modified since
              // is ignored.
              if (request.getHeader("If-None-Match") == null) {
                  
                  Date date = null;
                  
                  // Parsing the HTTP Date
                  for (int i = 0; (date == null) && (i < formats.length); i++) {
                      try {
                          date = formats[i].parse(headerValue);
                      } catch (ParseException e) {
                          ;
                      }
                  }
                  
                  if ((date != null) 
                      && (lastModified <= (date.getTime() + 1000)) ) {
                      // The entity has not been modified since the date 
                      // specified by the client. This is not an error case.
                      response.sendError
                          (HttpServletResponse.SC_NOT_MODIFIED);
                      return false;
                  }
                  
              }
              
          }
          
          // Checking If-None-Match
          headerValue = request.getHeader("If-None-Match");
          if (headerValue != null) {
              if (headerValue.indexOf("*") == -1) {
                  
                  commaTokenizer = new StringTokenizer(headerValue, ",");
                  boolean conditionSatisfied = false;
                  
                  while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
                      String currentToken = commaTokenizer.nextToken();
                      if (currentToken.trim().equals(eTag))
                          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.getMethod()))
                           || ("HEAD".equals(request.getMethod())) ) {
                          response.sendError
                              (HttpServletResponse.SC_NOT_MODIFIED);
                          return false;
                      } else {
                          response.sendError
                              (HttpServletResponse.SC_PRECONDITION_FAILED);
                          return false;
                      }
                  }
                  
              } else {
                  if (resourceInfo.exists()) {
                      
                  }
              }
          }
          
          // Checking If-Unmodified-Since
          headerValue = request.getHeader("If-Unmodified-Since");
          if (headerValue != null) {
              
              Date date = null;
              
              // Parsing the HTTP Date
              for (int i = 0; (date == null) && (i < formats.length); i++) {
                  try {
                      date = formats[i].parse(headerValue);
                  } catch (ParseException e) {
                      ;
                  }
              }
              
              if ( (date != null) && (lastModified > date.getTime()) ) {
                  // The entity has not been modified since the date 
                  // specified by the client. This is not an error case.
                  response.sendError
                      (HttpServletResponse.SC_PRECONDITION_FAILED);
                  return false;
              }
              
          }
          
          return true;
      }
  
  
      /**
       * Get the ETag value associated with a file.
       * 
       * @param resourceInfo File object
       * @param strong True if we want a strong ETag, in which case a checksum
       * of the file has to be calculated
       */
      protected String getETagValue(ResourceInfo resourceInfo, boolean strong) {
          // FIXME : Compute a strong ETag if requested, using an MD5 digest
          // of the file contents
          return resourceInfo.length + "-" + resourceInfo.date;
      }
  
  
      /**
       * Get the ETag associated with a file.
       * 
       * @param resourceInfo File object
       * @param strong True if we want a strong ETag, in which case a checksum
       * of the file has to be calculated
       */
      protected String getETag(ResourceInfo resourceInfo, boolean strong) {
          if (strong)
              return "\"" + getETagValue(resourceInfo, strong) + "\"";
          else
              return "W/\"" + getETagValue(resourceInfo, strong) + "\"";
      }
  
  
      // -------------------------------------------------------- Private Methods
  
  
      /**
       * 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
       *
       * @exception IOException if an input/output error occurs
       */
      private void copy(ResourceInfo resourceInfo, ServletOutputStream ostream)
  	throws IOException {
  
          IOException exception = null;
              
          // FIXME : i18n ?
          InputStream resourceInputStream = 
              resourceInfo.resources.getResourceAsStream(resourceInfo.path);
          InputStream istream = new BufferedInputStream
              (resourceInputStream, input);
          
          // Copy the input stream to the output stream
          exception = 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 writer The writer to write to
       *
       * @exception IOException if an input/output error occurs
       */
      private void copy(ResourceInfo resourceInfo, PrintWriter writer)
  	throws IOException {
  
          IOException exception = null;
              
          InputStream resourceInputStream = 
              resourceInfo.resources.getResourceAsStream(resourceInfo.path);
          // FIXME : i18n ?
          Reader reader = new InputStreamReader(resourceInputStream);
          
          // 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 ResourceInfo object
       * @param ostream The output stream to write to
       * @param range Range the client wanted to retrieve
       * @exception IOException if an input/output error occurs
       */
      private void copy(ResourceInfo resourceInfo, ServletOutputStream ostream, 
                        Range range)
  	throws IOException {
          
          IOException exception = null;
          
          InputStream resourceInputStream = 
              resourceInfo.resources.getResourceAsStream(resourceInfo.path);
          InputStream istream =
              new BufferedInputStream(resourceInputStream, input);
          exception = copyRange(istream, ostream, range.start, range.end);
          
  	// 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 resourceInfo The ResourceInfo object
       * @param writer The writer to write to
       * @param range Range the client wanted to retrieve
       * @exception IOException if an input/output error occurs
       */
      private void copy(ResourceInfo resourceInfo, PrintWriter writer, 
                        Range range)
  	throws IOException {
          
          IOException exception = null;
          
          InputStream resourceInputStream = 
              resourceInfo.resources.getResourceAsStream(resourceInfo.path);
          Reader reader = new InputStreamReader(resourceInputStream);
          exception = copyRange(reader, writer, range.start, range.end);
          
  	// Clean up the input stream
  	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 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
       */
      private void copy(ResourceInfo resourceInfo, ServletOutputStream ostream,
                        Enumeration ranges, String contentType)
  	throws IOException {
          
          IOException exception = null;
          
          while ( (exception == null) && (ranges.hasMoreElements()) ) {
              
              InputStream resourceInputStream = 
                  resourceInfo.resources.getResourceAsStream(resourceInfo.path);
              InputStream istream =	// FIXME: internationalization???????
                  new BufferedInputStream(resourceInputStream, input);
          
              Range currentRange = (Range) ranges.nextElement();
              
              // Writing MIME header.
              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 = copyRange(istream, ostream, currentRange.start,
                                    currentRange.end);
              
              try {
                  istream.close();
              } catch (Throwable t) {
                  ;
              }
              
          }
          
          ostream.print("--" + mimeSeparation + "--");
          
          // 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 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
       */
      private void copy(ResourceInfo resourceInfo, PrintWriter writer,
                        Enumeration ranges, String contentType)
  	throws IOException {
          
          IOException exception = null;
          
          while ( (exception == null) && (ranges.hasMoreElements()) ) {
              
              InputStream resourceInputStream = 
                  resourceInfo.resources.getResourceAsStream(resourceInfo.path);
              Reader reader = new InputStreamReader(resourceInputStream);
          
              Range currentRange = (Range) ranges.nextElement();
              
              // Writing MIME header.
              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 = copyRange(reader, writer, currentRange.start,
                                    currentRange.end);
              
              try {
                  reader.close();
              } catch (Throwable t) {
                  ;
              }
              
          }
          
          writer.print("--" + mimeSeparation + "--");
          
  	// 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 occured during processing
       */
      private IOException copyRange(InputStream istream, 
                                    ServletOutputStream 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 occured during processing
       */
      private 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 occured during processing
       */
      private IOException copyRange(InputStream istream, 
                                    ServletOutputStream 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 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 occured during processing
       */
      private 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;
          
      }
  
  
      /**
       * Display the size of a file.
       */
      private void displaySize(StringBuffer buf, int filesize) {
          
  	int leftside = filesize / 1024;
  	int rightside = (filesize % 1024) / 103;  // makes 1 digit
  	// To avoid 0.0 for non-zero file, we bump to 0.1
  	if (leftside == 0 && rightside == 0 && filesize != 0) 
  	    rightside = 1;
  	buf.append(leftside).append(".").append(rightside);
  	buf.append(" KB");
          
      }
      
      
      /**
       * Check to see if a default page exists.
       * 
       * @param pathname Pathname of the file to be served
       */
      private ResourceInfo checkWelcomeFiles(String pathname, 
                                             Resources resources) {
          
          String collectionName = pathname;
          if (!pathname.endsWith("/")) {
              collectionName += "/";
          }
              
  	// Refresh our currently defined set of welcome files
  	synchronized (welcomes) {
  	    welcomes = (String[]) getServletContext().getAttribute
  		(WELCOME_FILES_ATTR);
  	    if (welcomes == null)
  		welcomes = new String[0];
  	}
  
          // Serve a welcome resource or file if one exists
          for (int i = 0; i < welcomes.length; i++) {
              
              // Does the specified resource exist?
              String resourceName = collectionName + welcomes[i];
              ResourceInfo resourceInfo = 
                  new ResourceInfo(resourceName, resources);
              if (resourceInfo.exists()) {
                  return resourceInfo;
              }
              
          }
          
          return null;
          
      }
  
  
      /**
       * 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
       */
      private void serveResource(HttpServletRequest request,
                                 HttpServletResponse response,
                                 boolean content)
  	throws IOException, ServletException {
  
          // Identify the requested resource path
          String path = getRelativePath(request);
  	if (debug > 0) {
  	    if (content)
  		log("DefaultServlet.serveResource:  Serving resource '" +
  		    path + "' headers and data");
  	    else
  		log("DefaultServlet.serveResource:  Serving resource '" +
  		    path + "' headers only");
  	}
  
  	// Exclude any resource in the /WEB-INF and /META-INF subdirectories
  	// (the "toUpperCase()" avoids problems on Windows systems)
  	if (path.toUpperCase().startsWith("/WEB-INF") ||
  	    path.toUpperCase().startsWith("/META-INF")) {
  	    response.sendError(HttpServletResponse.SC_NOT_FOUND, path);
  	    return;
  	}
  
          Resources resources = getResources();
          ResourceInfo resourceInfo = new ResourceInfo(path, resources);
  
          if (!resourceInfo.exists) {
  	    response.sendError(HttpServletResponse.SC_NOT_FOUND, path);
  	    return;
          }
  
          // If the resource is not a collection, and the resource path
          // ends with "/" or "\", return NOT FOUND
          if (!resourceInfo.collection) {
              if (path.endsWith("/") || (path.endsWith("\\"))) {
                  response.sendError(HttpServletResponse.SC_NOT_FOUND, path);
                  return;
              }
          }
  
          // If the resource is a collection (aka a directory), we check 
          // the welcome files list.
          if (resourceInfo.collection) {
  
  	    if (!request.getRequestURI().endsWith("/")) {
  	        response.sendRedirect(request.getRequestURI() + "/");
  		return;
  	    }
  
              ResourceInfo welcomeFileInfo = checkWelcomeFiles(path, resources);
              if (welcomeFileInfo != null) {
                  String redirectPath = welcomeFileInfo.path;
                  String contextPath = request.getContextPath();
                  if ((contextPath != null) && (!contextPath.equals("/"))) {
                      redirectPath = contextPath + redirectPath;
                  }
                  response.sendRedirect(redirectPath);
                  return;
              }
              
          }
          
  	if (!resourceInfo.exists()) {
  	    response.sendError(HttpServletResponse.SC_NOT_FOUND, 
                                 resourceInfo.path);
  	    return;
  	}
  
          // Checking If headers
          if ( !checkIfHeaders(request, response, resourceInfo) )
              return;
          
          // Find content type.
          String contentType = 
              getServletContext().getMimeType(resourceInfo.path);
          
          if (resourceInfo.collection) {
              // Skip directory listings if we have been configured to 
              // suppress them
              if (!listings) {
                  response.sendError(HttpServletResponse.SC_NOT_FOUND,
                                     resourceInfo.path);
                  return;
              }
              contentType = "text/html";
          }
  
  
          // Parse range specifier
          Vector ranges = null;
          if (!resourceInfo.collection) {
              ranges = parseRange(request, response, resourceInfo);
          
              // Last-Modified header
              if (debug > 0)
                  log("DefaultServlet.serveFile:  lastModified='" +
                      (new Timestamp(resourceInfo.date)).toString() + "'");
              response.setDateHeader("Last-Modified", resourceInfo.date);
              
              // ETag header
              response.setHeader("ETag", getETag(resourceInfo, true));
          }
          
          ServletOutputStream ostream = null;
          PrintWriter writer = null;
          
          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;
                  }
              }
              
          }
          
          if ( ((ranges == null) || (ranges.isEmpty())) 
               && (request.getHeader("Range") == null) ) {
              
              // Set the appropriate output headers
              if (contentType != null) {
                  if (debug > 0)
                      log("DefaultServlet.serveFile:  contentType='" +
                          contentType + "'");
                  response.setContentType(contentType);
              }
              long contentLength = resourceInfo.length;
              if ((!resourceInfo.collection) && (contentLength >= 0)) {
                  if (debug > 0)
                      log("DefaultServlet.serveFile:  contentLength=" +
                          contentLength);
                  response.setContentLength((int) contentLength);
              }
              
              // Copy the input stream to our output stream (if requested)
              if (content) {
                  response.setBufferSize(output);
                  if (ostream != null) {
                      copy(resourceInfo, ostream);
                  } else {
                      copy(resourceInfo, writer);
                  }
              }
              
          } else {
              
              if ((ranges == null) || (ranges.isEmpty()))
                  return;
              
              // Partial content response.
              
              response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
              
              if (ranges.size() == 1) {
                  
                  Range range = (Range) ranges.elementAt(0);
                  response.addHeader("Content-Range", "bytes " 
                                     + range.start
                                     + "-" + range.end + "/" 
                                     + range.length);
                  
                  if (contentType != null) {
                      if (debug > 0)
                          log("DefaultServlet.serveFile:  contentType='" +
                              contentType + "'");
                      response.setContentType(contentType);
                  }
                  
                  if (content) {
                      response.setBufferSize(output);
                      if (ostream != null) {
                          copy(resourceInfo, ostream, range);
                      } else {
                          copy(resourceInfo, writer, range);
                      }
                  }
                  
              } else {
                  
                  response.setContentType("multipart/byteranges; boundary="
                                          + mimeSeparation);
                  
                  if (content) {
                      response.setBufferSize(output);
                      if (ostream != null) {
                          copy(resourceInfo, ostream, ranges.elements(), 
                               contentType);
                      } else {
                          copy(resourceInfo, writer, ranges.elements(), 
                               contentType);
                      }
                  }
                  
              }
              
          }
          
      }
  
  
      /**
       * Parse the range header.
       * 
       * @param request The servlet request we are processing
       * @param response The servlet response we are creating
       * @return Vector of ranges
       */
      private Vector parseRange(HttpServletRequest request, 
                                HttpServletResponse response, 
                                ResourceInfo resourceInfo) 
          throws IOException {
          
          // Checking If-Range
          String headerValue = request.getHeader("If-Range");
          if (headerValue != null) {
              
              String eTag = getETag(resourceInfo, true);
              long lastModified = resourceInfo.date;
              
              Date date = null;
              
              // Parsing the HTTP Date
              for (int i = 0; (date == null) && (i < formats.length); i++) {
                  try {
                      date = formats[i].parse(headerValue);
                  } catch (ParseException e) {
                      ;
                  }
              }
              
              if (date == null) {
                  
                  // If the ETag the client gave does not match the entity
                  // etag, then the entire entity is returned.
                  if (!eTag.equals(headerValue.trim()))
                      return null;
                  
              } 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 > (date.getTime() + 1000))
                      return null;
                  
              }
              
          }
          
          long fileLength = resourceInfo.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")) {
              response.sendError
                  (HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
              return null;
          }
          
          rangeHeader = rangeHeader.substring(6);
          
          // Vector which will contain all the ranges which are successfully
          // parsed.
          Vector result = new Vector();
          StringTokenizer commaTokenizer = new StringTokenizer(rangeHeader, ",");
          
          // Parsing the range list
          while (commaTokenizer.hasMoreTokens()) {
              String rangeDefinition = commaTokenizer.nextToken();
              
              Range currentRange = new Range();
              currentRange.length = fileLength;
              
              int dashPos = rangeDefinition.indexOf('-');
              
              if (dashPos == -1) {
                  response.sendError
                      (HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
                  return null;
              }
              
              if (dashPos == 0) {
                  
                  try {
                      long offset = Long.parseLong(rangeDefinition);
                      currentRange.start = fileLength + offset;
                      currentRange.end = fileLength - 1;
                  } catch (NumberFormatException e) {
                      response.sendError
                          (HttpServletResponse
                           .SC_REQUESTED_RANGE_NOT_SATISFIABLE);
                      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) {
                      response.sendError
                          (HttpServletResponse
                           .SC_REQUESTED_RANGE_NOT_SATISFIABLE);
                      return null;
                  }
                  
              }
              
              if (!currentRange.validate()) {
                  response.sendError
                      (HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
                  return null;
              }
              
              result.addElement(currentRange);
          }
          
          return result;
      }
  
  
      // ------------------------------------------------------ Range Inner Class
  
  
      private class Range {
          
          public long start;
          public long end;
          public long length;
          
          /**
           * Validate range.
           */
          public boolean validate() {
              return ( (start >= 0) && (end >= 0) && (length > 0)
                       && (start <= end) && (end < length) );
          }
          
      }
  
  
      // ----------------------------------------------  ResourceInfo Inner Class
  
  
      protected class ResourceInfo {
  
  
          /**
           * Constructor.
           * 
           * @param pathname Path name of the file
           */
          public ResourceInfo(String path, Resources resources) {
              
              this.path = path;
              this.resources = resources;
              this.exists = resources.exists(path);
              if (exists) {
                  this.creationDate = resources.getResourceCreated(path);
                  this.date = resources.getResourceModified(path);
                  this.httpDate = formats[0].format(new Date(date));
                  this.length = resources.getResourceLength(path);
                  this.collection = resources.isCollection(path);
              }
  
          }
  
  
          public String path;
          public long creationDate;
          public String httpDate;
          public long date;
          public long length;
          public boolean collection;
          public boolean exists;
          public Resources resources;
  
  
          /**
           * Test if the associated resource exists.
           */
          public boolean exists() {
              return exists;
          }
  
  
          /**
           * String representation.
           */
          public String toString() {
              return path;
          }
  
  
      }
  
  
  }
  
  
  
  1.1                  jakarta-tomcat/src/webdav/org/apache/tomcat/webdav/LocalStrings.properties
  
  Index: LocalStrings.properties
  ===================================================================
  defaultservlet.directorylistingfor=Directory Listing for:
  defaultservlet.upto=Up to:
  defaultservlet.subdirectories=Subdirectories:
  defaultservlet.files=Files:
  webdavservlet.jaxpfailed=JAXP initialization failed
  
  
  
  1.1                  jakarta-tomcat/src/webdav/org/apache/tomcat/webdav/WebdavServlet.java
  
  Index: WebdavServlet.java
  ===================================================================
  /*
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */
  package org.apache.tomcat.webdav;
  
  
  import java.io.BufferedInputStream;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.InputStream;
  import java.io.IOException;
  import java.io.PrintWriter;
  import java.io.StringWriter;
  import java.io.Writer;
  import java.net.MalformedURLException;
  import java.net.URL;
  import java.sql.Timestamp;
  import java.util.Date;
  import java.util.Enumeration;
  import java.util.Vector;
  import java.util.Stack;
  import java.util.StringTokenizer;
  import java.util.Locale;
  import java.util.Hashtable;
  import java.text.ParseException;
  import java.text.SimpleDateFormat;
  import java.security.MessageDigest;
  import java.security.NoSuchAlgorithmException;
  import javax.servlet.RequestDispatcher;
  import javax.servlet.ServletException;
  import javax.servlet.ServletContext;
  import javax.servlet.ServletOutputStream;
  import javax.servlet.http.HttpServlet;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  import org.w3c.dom.Node;
  import org.w3c.dom.NodeList;
  import org.w3c.dom.Element;
  import org.w3c.dom.Document;
  import org.xml.sax.InputSource;
  import javax.xml.parsers.DocumentBuilder;
  import javax.xml.parsers.DocumentBuilderFactory;
  import javax.xml.parsers.ParserConfigurationException;
  import org.apache.tomcat.webdav.util.*;
  import org.apache.tomcat.webdav.resources.*;
  // import org.apache.catalina.Resources;
  // import org.apache.catalina.resources.ResourceBean;
  // import org.apache.catalina.resources.DirectoryBean;
  // import org.apache.catalina.util.MD5Encoder;
  // import org.apache.catalina.util.StringManager;
  // import org.apache.catalina.util.XMLWriter;
  // import org.apache.catalina.util.DOMWriter;
  // import org.apache.catalina.util.xml.SaxContext;
  // import org.apache.catalina.util.xml.XmlAction;
  // import org.apache.catalina.util.xml.XmlMapper;
  
  
  /**
   * Servlet which adds support for WebDAV level 2. All the basic HTTP requests
   * are handled by the DefaultServlet.
   *
   * @author Remy Maucherat
   * @version $Revision: 1.1 $ $Date: 2000/11/03 21:27:37 $
   */
  
  public class WebdavServlet
      extends DefaultServlet {
  
      // -------------------------------------------------------------- Constants
  
  
      private static final String METHOD_HEAD = "HEAD";
      private static final String METHOD_PROPFIND = "PROPFIND";
      private static final String METHOD_PROPPATCH = "PROPPATCH";
      private static final String METHOD_MKCOL = "MKCOL";
      private static final String METHOD_COPY = "COPY";
      private static final String METHOD_MOVE = "MOVE";
      private static final String METHOD_LOCK = "LOCK";
      private static final String METHOD_UNLOCK = "UNLOCK";
      
  
      /**
       * Default depth is infite.
       */
      private static final int INFINITY = 3; // To limit tree browsing a bit
      
      
      /**
       * PROPFIND - Specify a property mask.
       */
      private static final int FIND_BY_PROPERTY = 0;
      
      
      /**
       * PROPFIND - Display all properties.
       */
      private static final int FIND_ALL_PROP = 1;
      
      
      /**
       * PROPFIND - Return property names.
       */
      private static final int FIND_PROPERTY_NAMES = 2;
  
  
      /**
       * Create a new lock.
       */
      private static final int LOCK_CREATION = 0;
      
      
      /**
       * Refresh lock.
       */
      private static final int LOCK_REFRESH = 1;
      
      
      /**
       * Default lock timeout value.
       */
      private static final int DEFAULT_TIMEOUT = 3600;
      
      
      /**
       * Maximum lock timeout.
       */
      private static final int MAX_TIMEOUT = 604800;
      
      
      /**
       * Default namespace.
       */
      protected static final String DEFAULT_NAMESPACE = "DAV:";
      
      
      // ----------------------------------------------------- Instance Variables
  
  
      /**
       * JAXP Document builder.
       */
      private DocumentBuilder documentBuilder;
  
  
      /**
       * Repository of the locks put on single resources.
       * <p>
       * Key : path <br>
       * Value : LockInfo
       */
      private Hashtable resourceLocks = new Hashtable();
  
  
      /**
       * Repository of the lock-null resources.
       * <p>
       * Key : path of the collection containing the lock-null resource<br>
       * Value : Vector of lock-null resource which are members of the 
       * collection. Each element of the Vector is the path associated with
       * the lock-null resource.
       */
      private Hashtable lockNullResources = new Hashtable();
  
  
      /**
       * Vector of the heritable locks.
       * <p>
       * Key : path <br>
       * Value : LockInfo
       */
      private Vector collectionLocks = new Vector();
  
  
      /**
       * Secret information used to generate reasonably secure lock ids.
       */
      private String secret = "catalina";
  
  
      // --------------------------------------------------------- Public Methods
          /**
       * Return the relative path associated with this servlet.
       *
       * @param request The servlet request we are processing
       */
      protected String getRelativePath(HttpServletRequest request) {
          
          // Are we being processed by a RequestDispatcher.include()?
          if (request.getAttribute("javax.servlet.include.request_uri")!=null) {
              String result = (String)
                  request.getAttribute("javax.servlet.include.path_info");
              if (result == null)
                  result = (String)
                      request.getAttribute("javax.servlet.include.servlet_path");
              if ((result == null) || (result.equals("")))
                  result = "/";
              return (result);
          }
  
          // No, extract the desired path directly from the request
          String result = request.getPathInfo();
          if (result == null) {
              result = request.getServletPath();
          }
          if ((result == null) || (result.equals(""))) {
              result = "/";
          }
          return result;
          
      }
  
      /**
       * Initialize this servlet.
       */
      public void init() 
          throws ServletException {
  
          super.init();
  
          String value = null;
  	try {
  	    value = getServletConfig().getInitParameter("secret");
              if (value != null)
                  secret = value;
          } catch (Throwable t) {
  	    ;
  	}
          
          try {
              documentBuilder = 
                  DocumentBuilderFactory.newInstance().newDocumentBuilder();
          } catch(ParserConfigurationException e) {
              throw new ServletException
                  (sm.getString("webdavservlet.jaxpfailed"));
          }
  
      }
  
      // ------------------------------------------------------ Protected Methods
  
  
      /**
       * Handles the special WebDAV methods.
       */
      protected void service(HttpServletRequest req, HttpServletResponse resp)
  	throws ServletException, IOException {
  
  	String method = req.getMethod();
  
          if (debug > 0) {
              String path = getRelativePath(req);
              System.out.println("[" + method + "] " + path);
          }
          
  	if (method.equals(METHOD_PROPFIND)) {
  	    doPropfind(req, resp);
          } else if (method.equals(METHOD_PROPPATCH)) {
              doProppatch(req, resp);
          } else if (method.equals(METHOD_MKCOL)) {
              doMkcol(req, resp);
          } else if (method.equals(METHOD_COPY)) {
              doCopy(req, resp);
          } else if (method.equals(METHOD_MOVE)) {
              doMove(req, resp);
          } else if (method.equals(METHOD_LOCK)) {
              doLock(req, resp);
          } else if (method.equals(METHOD_UNLOCK)) {
              doUnlock(req, resp);
          } else {
              // DefaultServlet processing
              super.service(req, resp);
          }
          
      }
  
  
      /**
       * 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 resourceInfo File object
       * @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(HttpServletRequest request,
                                       HttpServletResponse response, 
                                       ResourceInfo resourceInfo)
          throws IOException {
  
          if (!super.checkIfHeaders(request, response, resourceInfo))
              return false;
  
          // TODO : Checking the WebDAV If header
          return true;
  
      }
  
  
      /**
       * OPTIONS Method.
       */
      protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
  	throws ServletException, IOException {
          
          String path = getRelativePath(req);
          
  	resp.addHeader("DAV", "1,2");
          String methodsAllowed = null;
          
          Resources resources = getResources();
          
  	if (!resources.exists(path)) {
  	    methodsAllowed = "OPTIONS, MKCOL, PUT, LOCK";
              resp.addHeader("Allow", methodsAllowed);
              return;
  	}
          
          methodsAllowed = "OPTIONS, GET, HEAD, POST, DELETE, TRACE, " 
              + "PROPFIND, PROPPATCH, COPY, MOVE, LOCK, UNLOCK";
          if (!resources.isCollection(path)) {
              methodsAllowed += ", PUT";
          }
          
          resp.addHeader("Allow", methodsAllowed);
          
      }
  
  
      /**
       * PROPFIND Method.
       */
      protected void doPropfind(HttpServletRequest req, HttpServletResponse resp)
  	throws ServletException, IOException {
  
          if (!listings) {
              resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
              return;
          }
  
          String path = getRelativePath(req);
          
          // Properties which are to be displayed.
          Vector properties = null;
          // Propfind depth
          int depth = INFINITY;
          // Propfind type
          int type = FIND_ALL_PROP;
          
  	String depthStr = req.getHeader("Depth");
  	
  	if (depthStr == null) {
  	    depth = INFINITY;
  	} else {
  	    if (depthStr.equals("0")) {
                  depth = 0;
  	    } else if (depthStr.equals("1")) {
                  depth = 1;
  	    } else if (depthStr.equals("infinity")) {
                  depth = INFINITY;
  	    }
  	}
  
          Node propNode = null;
          
          try {
              Document document = documentBuilder.parse
                  (new InputSource(req.getInputStream()));
              
              // Get the root element of the document
              Element rootElement = document.getDocumentElement();
              NodeList childList = rootElement.getChildNodes();
              
              for (int i=0; i < childList.getLength(); i++) {
                  Node currentNode = childList.item(i);
                  switch (currentNode.getNodeType()) {
                  case Node.TEXT_NODE:
                      break;
                  case Node.ELEMENT_NODE:
                      if (currentNode.getNodeName().endsWith("prop")) {
                          type = FIND_BY_PROPERTY;
                          propNode = currentNode;
                      }
                      if (currentNode.getNodeName().endsWith("propname")) {
                          type = FIND_PROPERTY_NAMES;
                      }
                      if (currentNode.getNodeName().endsWith("allprop")) {
                          type = FIND_ALL_PROP;
                      }
                      break;
                  }
              }
          } catch(Exception e) {
              // Most likely there was no content : we use the defaults.
              // TODO : Enhance that !
          }
          
          if (type == FIND_BY_PROPERTY) {
              properties = new Vector();
              NodeList childList = propNode.getChildNodes();
              
              for (int i=0; i < childList.getLength(); i++) {
                  Node currentNode = childList.item(i);
                  switch (currentNode.getNodeType()) {
                  case Node.TEXT_NODE:
                      break;
                  case Node.ELEMENT_NODE:
                      String nodeName = currentNode.getNodeName();
                      String propertyName = null;
                      if (nodeName.indexOf(':') != -1) {
                          propertyName = nodeName.substring
                              (nodeName.indexOf(':') + 1);
                      } else {
                          propertyName = nodeName;
                      }
                      // href is a live property which is handled differently
                      properties.addElement(propertyName);
                      break;
                  }
              }
              
          }
          
          Resources resources = getResources();
          
  	if (!resources.exists(path)) {
  	    resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
  	    return;
  	}
  
          resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
  
          // Create multistatus object
          XMLWriter generatedXML = new XMLWriter();
          
          generatedXML.writeElement(null, "multistatus" 
                                    + generateNamespaceDeclarations(), 
                                    XMLWriter.OPENING);
          
          if (depth == 0) {
              parseProperties(req, resources, generatedXML, path, type, 
                              properties);
          } else {
              // The stack always contains the object of the current level
              Stack stack = new Stack();
              stack.push(path);
              
              // Stack of the objects one level below
              Stack stackBelow = new Stack();
              
              while ((!stack.isEmpty()) && (depth >= 0)) {
                  
                  String currentPath = (String) stack.pop();
                  parseProperties(req, resources, generatedXML, currentPath, 
                                  type, properties);
                  
                  if (resources.isCollection(currentPath)) {
                      String[] children = 
                          resources.getCollectionMembers(currentPath);
                      
                      for (int i=0; i<children.length; i++) {
                          stackBelow.push(children[i]);
                      }
                      
                      if (depth > 0) {
                          // Displaying the lock-null resources present in that 
                          // collection
                          Vector currentLockNullResources = 
                              (Vector) lockNullResources.get(currentPath);
                          if (currentLockNullResources != null) {
                              Enumeration lockNullResourcesList =
                                  currentLockNullResources.elements();
                              while (lockNullResourcesList.hasMoreElements()) {
                                  String lockNullPath = (String) 
                                      lockNullResourcesList.nextElement();
                                  parseLockNullProperties
                                      (req, generatedXML, currentPath, type, 
                                       properties);
                              }
                          }
                      }
                  }
                  
                  if (stack.isEmpty()) {
                      depth--;
                      stack = stackBelow;
                      stackBelow = new Stack();
                  }
                  
              }
          }
          
          generatedXML.writeElement(null, "multistatus", 
                                    XMLWriter.CLOSING);
          
          Writer writer = resp.getWriter();
          writer.write(generatedXML.toString());
          writer.flush();
          
      }
  
  
      /**
       * PROPPATCH Method.
       */
      protected void doProppatch(HttpServletRequest req, 
                                 HttpServletResponse resp)
  	throws ServletException, IOException {
  
          if (readOnly) {
              resp.sendError(WebdavStatus.SC_FORBIDDEN);
              return;
          }
  
          if (isLocked(req)) {
              resp.sendError(WebdavStatus.SC_LOCKED);
              return;
          }
          
          
          
      }
  
  
      /**
       * MKCOL Method.
       */
      protected void doMkcol(HttpServletRequest req, HttpServletResponse resp)
  	throws ServletException, IOException {
  
          if (readOnly) {
              resp.sendError(WebdavStatus.SC_FORBIDDEN);
              return;
          }
  
          if (isLocked(req)) {
              resp.sendError(WebdavStatus.SC_LOCKED);
              return;
          }
          
          String path = getRelativePath(req);
          
          Resources resources = getResources();
          
  	// Can't create a collection if a resource already exists at the given
          // path
  	if (resources.exists(path)) {
  	    resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
  	    return;
  	}
  
          boolean result = resources.createCollection(path);
          
          if (!result) {
              resp.sendError(WebdavStatus.SC_CONFLICT,
                             WebdavStatus.getStatusText
                             (WebdavStatus.SC_CONFLICT));
          } else {
              resp.setStatus(WebdavStatus.SC_CREATED);
              // Removing any lock-null resource which would be present
              lockNullResources.remove(path);
          }
  
      }
  
  
      /**
       * DELETE Method.
       */
      protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
  	throws ServletException, IOException {
  
          if (readOnly) {
              resp.sendError(WebdavStatus.SC_FORBIDDEN);
              return;
          }
          
          if (isLocked(req)) {
              resp.sendError(WebdavStatus.SC_LOCKED);
              return;
          }
          
          deleteResource(req, resp);
          
      }
  
  
      /**
       * Process a POST request for the specified resource.
       *
       * @param request The servlet request we are processing
       * @param response The servlet response we are creating
       *
       * @exception IOException if an input/output error occurs
       * @exception ServletException if a servlet-specified error occurs
       */
      protected void doPut(HttpServletRequest req, HttpServletResponse resp)
  	throws ServletException, IOException {
          
          if (isLocked(req)) {
              resp.sendError(WebdavStatus.SC_LOCKED);
              return;
          }
          
          super.doPut(req, resp);
          
          String path = getRelativePath(req);
          
          // Removing any lock-null resource which would be present
          lockNullResources.remove(path);
          
      }
  
      /**
       * COPY Method.
       */
      protected void doCopy(HttpServletRequest req, HttpServletResponse resp)
  	throws ServletException, IOException {
  
          if (readOnly) {
              resp.sendError(WebdavStatus.SC_FORBIDDEN);
              return;
          }
          
          copyResource(req, resp);
          
      }
  
  
      /**
       * MOVE Method.
       */
      protected void doMove(HttpServletRequest req, HttpServletResponse resp)
  	throws ServletException, IOException {
  
          if (readOnly) {
              resp.sendError(WebdavStatus.SC_FORBIDDEN);
              return;
          }
          
          if (isLocked(req)) {
              resp.sendError(WebdavStatus.SC_LOCKED);
              return;
          }
          
          String path = getRelativePath(req);
          
          if (copyResource(req, resp)) {
              deleteResource(path, req, resp);
          }
          
      }
  
  
      /**
       * LOCK Method.
       */
      protected void doLock(HttpServletRequest req, HttpServletResponse resp)
  	throws ServletException, IOException {
          
          if (readOnly) {
              resp.sendError(WebdavStatus.SC_FORBIDDEN);
              return;
          }
  
          if (isLocked(req)) {
              resp.sendError(WebdavStatus.SC_LOCKED);
              return;
          }
          
          LockInfo lock = new LockInfo();
          
          // Parsing lock request
          
          // Parsing depth header
          
  	String depthStr = req.getHeader("Depth");
  	
  	if (depthStr == null) {
  	    lock.depth = INFINITY;
  	} else {
  	    if (depthStr.equals("0")) {
  		lock.depth = 0;
  	    } else {
                  lock.depth = INFINITY;
  	    }
  	}
          
          // Parsing timeout header
          
          int lockDuration = DEFAULT_TIMEOUT;
          String lockDurationStr = req.getHeader("Timeout");
          if (lockDurationStr == null) {
              lockDuration = DEFAULT_TIMEOUT;
          } else {
              if (lockDurationStr.startsWith("Second-")) {
                  lockDuration = 
                      (new Integer(lockDurationStr.substring(7))).intValue();
              } else {
                  if (lockDurationStr.equalsIgnoreCase("infinity")) {
                      lockDuration = MAX_TIMEOUT;
                  } else {
                      try {
                          lockDuration = 
                              (new Integer(lockDurationStr)).intValue();
                      } catch (NumberFormatException e) {
                          lockDuration = MAX_TIMEOUT;
                      }
                  }
              }
              if (lockDuration == 0) {
                  lockDuration = DEFAULT_TIMEOUT;
              }
              if (lockDuration > MAX_TIMEOUT) {
                  lockDuration = MAX_TIMEOUT;
              }
          }
          lock.expiresAt = System.currentTimeMillis() + (lockDuration * 1000);
          
          int lockRequestType = LOCK_CREATION;
          
          Node lockInfoNode = null;
          
          try {
              Document document = documentBuilder.parse(new InputSource
                  (req.getInputStream()));
              
              // Get the root element of the document
              Element rootElement = document.getDocumentElement();
              lockInfoNode = rootElement;
          } catch(Exception e) {
              lockRequestType = LOCK_REFRESH;
          }
          
          if (lockInfoNode != null) {
              
              // Reading lock information
              
              NodeList childList = lockInfoNode.getChildNodes();
              StringWriter strWriter = null;
              DOMWriter domWriter = null;
              
              Node lockScopeNode = null;
              Node lockTypeNode = null;
              Node lockOwnerNode = null;
              
              for (int i=0; i < childList.getLength(); i++) {
                  Node currentNode = childList.item(i);
                  switch (currentNode.getNodeType()) {
                  case Node.TEXT_NODE:
                      break;
                  case Node.ELEMENT_NODE:
                      String nodeName = currentNode.getNodeName();
                      if (nodeName.endsWith("lockscope")) {
                          lockScopeNode = currentNode;
                      }
                      if (nodeName.endsWith("locktype")) {
                          lockTypeNode = currentNode;
                      }
                      if (nodeName.endsWith("owner")) {
                          lockOwnerNode = currentNode;
                      }
                      break;
                  }
              }
              
              if (lockScopeNode != null) {
                  
                  childList = lockScopeNode.getChildNodes();
                  for (int i=0; i < childList.getLength(); i++) {
                      Node currentNode = childList.item(i);
                      switch (currentNode.getNodeType()) {
                      case Node.TEXT_NODE:
                          break;
                      case Node.ELEMENT_NODE:
                          String tempScope = currentNode.getNodeName();
                          if (tempScope.indexOf(':') != -1) {
                              lock.scope = 
                                  tempScope.substring(tempScope.indexOf(':'));
                          } else {
                              lock.scope = tempScope;
                          }
                          break;
                      }
                  }
                  
                  if (lock.scope == null) {
                      // Bad request
                      resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
                  }
                  
              } else {
                  // Bad request
                  resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
              }
              
              if (lockTypeNode != null) {
                  
                  childList = lockTypeNode.getChildNodes();
                  for (int i=0; i < childList.getLength(); i++) {
                      Node currentNode = childList.item(i);
                      switch (currentNode.getNodeType()) {
                      case Node.TEXT_NODE:
                          break;
                      case Node.ELEMENT_NODE:
                          String tempType = currentNode.getNodeName();
                          if (tempType.indexOf(':') != -1) {
                              lock.type = 
                                  tempType.substring(tempType.indexOf(':') + 1);
                          } else {
                              lock.type = tempType;
                          }
                          break;
                      }
                  }
                  
                  if (lock.type == null) {
                      // Bad request
                      resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
                  }
                  
              } else {
                  // Bad request
                  resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
              }
              
              if (lockOwnerNode != null) {
                  
                  childList = lockOwnerNode.getChildNodes();
                  for (int i=0; i < childList.getLength(); i++) {
                      Node currentNode = childList.item(i);
                      switch (currentNode.getNodeType()) {
                      case Node.TEXT_NODE:
                          lock.owner += currentNode.getNodeValue();
                          break;
                      case Node.ELEMENT_NODE:
                          strWriter = new StringWriter();
                          domWriter = new DOMWriter(strWriter, true);
                          domWriter.print(currentNode);
                          lock.owner += strWriter.toString();
                          break;
                      }
                  }
                  
                  if (lock.owner == null) {
                      // Bad request
                      resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
                  }
                  
              } else {
                  lock.owner = new String();
              }
              
          }
          
          String path = getRelativePath(req);
          
          lock.path = path;
          
          Resources resources = getResources();
          
          Enumeration locksList = null;
          
          if (lockRequestType == LOCK_CREATION) {
              
              // Generating lock id
              String lockTokenStr = req.getServletPath() + "-" + lock.type + "-" 
                  + lock.scope + "-" + req.getUserPrincipal() + "-" 
                  + lock.depth + "-" + lock.owner + "-" + lock.tokens + "-" 
                  + lock.expiresAt + "-" + System.currentTimeMillis() + "-" 
                  + secret;
              String lockToken = 
                  md5Encoder.encode(md5Helper.digest(lockTokenStr.getBytes()));
              
              if ( (resources.exists(path)) && (resources.isCollection(path)) && 
                   (lock.depth == INFINITY) ) {
                  
                  // Locking a collection (and all its member resources)
                  
                  // Checking if a child resource of this collection is 
                  // already locked
                  Vector lockPaths = new Vector();
                  locksList = collectionLocks.elements();
                  while (locksList.hasMoreElements()) {
                      LockInfo currentLock = (LockInfo) locksList.nextElement();
                      if (currentLock.hasExpired()) {
                          resourceLocks.remove(currentLock.path);
                          continue;
                      }
                      if ( (currentLock.path.startsWith(lock.path)) &&
                           ((currentLock.isExclusive()) || 
                            (lock.isExclusive())) ) {
                          // A child collection of this collection is locked
                          lockPaths.addElement(currentLock.path);
                      }
                  }
                  locksList = resourceLocks.elements();
                  while (locksList.hasMoreElements()) {
                      LockInfo currentLock = (LockInfo) locksList.nextElement();
                      if (currentLock.hasExpired()) {
                          resourceLocks.remove(currentLock.path);
                          continue;
                      }
                      if ( (currentLock.path.startsWith(lock.path)) &&
                           ((currentLock.isExclusive()) || 
                            (lock.isExclusive())) ) {
                          // A child resource of this collection is locked
                          lockPaths.addElement(currentLock.path);
                      }
                  }
                  
                  if (!lockPaths.isEmpty()) {
                      
                      // One of the child paths was locked
                      // We generate a multistatus error report
                      
                      Enumeration lockPathsList = lockPaths.elements();
                      
                      resp.setStatus(WebdavStatus.SC_CONFLICT);
                      
                      XMLWriter generatedXML = new XMLWriter();
                      generatedXML.writeXMLHeader();
                      
                      generatedXML.writeElement
                          (null, "multistatus" + generateNamespaceDeclarations(),
                           XMLWriter.OPENING);
                      
                      while (lockPathsList.hasMoreElements()) {
                          generatedXML.writeElement(null, "response", 
                                                    XMLWriter.OPENING);
                          generatedXML.writeElement(null, "href", 
                                                    XMLWriter.OPENING);
                          generatedXML
                              .writeText((String) lockPathsList.nextElement());
                          generatedXML.writeElement(null, "href", 
                                                    XMLWriter.CLOSING);
                          generatedXML.writeElement(null, "status", 
                                                    XMLWriter.OPENING);
                          generatedXML
                              .writeText("HTTP/1.1 " + WebdavStatus.SC_LOCKED
                                         + " " + WebdavStatus
                                         .getStatusText(WebdavStatus.SC_LOCKED));
                          generatedXML.writeElement(null, "status", 
                                                    XMLWriter.CLOSING);
                          
                          generatedXML.writeElement(null, "response", 
                                                    XMLWriter.CLOSING);
                      }
                      
                      generatedXML.writeElement(null, "multistatus", 
                                            XMLWriter.CLOSING);
                      
                      Writer writer = resp.getWriter();
                      writer.write(generatedXML.toString());
                      writer.close();
                      
                      return;
                      
                  }
                  
                  boolean addLock = true;
                  
                  // Checking if there is already a shared lock on this path
                  locksList = collectionLocks.elements();
                  while (locksList.hasMoreElements()) {
                      
                      LockInfo currentLock = (LockInfo) locksList.nextElement();
                      if (currentLock.path.equals(lock.path)) {
                          
                          if (currentLock.isExclusive()) {
                              resp.sendError(WebdavStatus.SC_LOCKED);
                              return;
                          } else {
                              if (lock.isExclusive()) {
                                  resp.sendError(WebdavStatus.SC_LOCKED);
                                  return;
                              }
                          }
                          
                          currentLock.tokens.addElement(lockToken);
                          lock = currentLock;
                          addLock = false;
                          
                      }
                      
                  }
                  
                  if (addLock) {
                      lock.tokens.addElement(lockToken);
                      collectionLocks.addElement(lock);
                  }
                  
              } else {
                  
                  // Locking a single resource
                  
                  // Retrieving an already existing lock on that resource
                  LockInfo presentLock = (LockInfo) resourceLocks.get(lock.path);
                  if (presentLock != null) {
                      
                      if ( (presentLock.isExclusive()) || (lock.isExclusive()) ) {
                          // If either lock is exclusive, the lock can't be 
                          // granted
                          resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
                          return;
                      } else {
                          presentLock.tokens.addElement(lockToken);
                          lock = presentLock;
                      }
                      
                  } else {
                      
                      lock.tokens.addElement(lockToken);
                      resourceLocks.put(lock.path, lock);
                      
                      // Checking if a resource exists at this path
                      if (!resources.exists(lock.path)) {
                          
                          // "Creating" a lock-null resource
                          int slash = lock.path.lastIndexOf('/');
                          String parentPath = lock.path.substring(0, slash);
                          
                          Vector lockNulls = 
                              (Vector) lockNullResources.get(parentPath);
                          if (lockNulls == null) {
                              lockNulls = new Vector();
                              lockNullResources.put(parentPath, lockNulls);
                          }
                          
                          lockNulls.addElement(lock.path);
                          
                      }
                      
                  }
                  
              }
              
          }
          
          if (lockRequestType == LOCK_REFRESH) {
              
              String ifHeader = req.getHeader("If");
              if (ifHeader == null)
                  ifHeader = "";
              
              // Checking resource locks
              
              LockInfo toRenew = (LockInfo) resourceLocks.get(path);
              Enumeration tokenList = null;
              if (lock != null) {
                  
                  // At least one of the tokens of the locks must have been given
                  
                  tokenList = toRenew.tokens.elements();
                  while (tokenList.hasMoreElements()) {
                      String token = (String) tokenList.nextElement();
                      if (ifHeader.indexOf(token) != -1) {
                          toRenew.expiresAt = lock.expiresAt;
                          lock = toRenew;
                      }
                  }
                  
              }
              
              // Checking inheritable collection locks
              
              Enumeration collectionLocksList = collectionLocks.elements();
              while (collectionLocksList.hasMoreElements()) {
                  toRenew = (LockInfo) collectionLocksList.nextElement();
                  if (path.equals(toRenew.path)) {
                      
                      tokenList = toRenew.tokens.elements();
                      while (tokenList.hasMoreElements()) {
                          String token = (String) tokenList.nextElement();
                          if (ifHeader.indexOf(token) != -1) {
                              toRenew.expiresAt = lock.expiresAt;
                              lock = toRenew;
                          }
                      }
                      
                  }
              }
              
          }
          
          // Set the status, then generate the XML response containing 
          // the lock information
          XMLWriter generatedXML = new XMLWriter();
          generatedXML.writeXMLHeader();
          generatedXML.writeElement(null, "prop" 
                                    + generateNamespaceDeclarations(), 
                                    XMLWriter.OPENING);
          
          generatedXML.writeElement(null, "lockdiscovery", 
                                    XMLWriter.OPENING);
          
          lock.toXML(generatedXML, true);
          
          generatedXML.writeElement(null, "lockdiscovery", 
                                    XMLWriter.CLOSING);
          
          generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
          
          resp.setStatus(WebdavStatus.SC_OK);
          Writer writer = resp.getWriter();
          writer.write(generatedXML.toString());
          writer.close();
          
      }
  
  
      /**
       * UNLOCK Method.
       */
      protected void doUnlock(HttpServletRequest req, HttpServletResponse resp)
  	throws ServletException, IOException {
          
          if (readOnly) {
              resp.sendError(WebdavStatus.SC_FORBIDDEN);
              return;
          }
  
          if (isLocked(req)) {
              resp.sendError(WebdavStatus.SC_LOCKED);
              return;
          }
          
          String path = getRelativePath(req);
          
          String lockTokenHeader = req.getHeader("Lock-Token");
          if (lockTokenHeader == null)
              lockTokenHeader = "";
          
          // Checking resource locks
          
          LockInfo lock = (LockInfo) resourceLocks.get(path);
          Enumeration tokenList = null;
          if (lock != null) {
              
              // At least one of the tokens of the locks must have been given
              
              tokenList = lock.tokens.elements();
              while (tokenList.hasMoreElements()) {
                  String token = (String) tokenList.nextElement();
                  if (lockTokenHeader.indexOf(token) != -1) {
                      lock.tokens.removeElement(token);
                  }
              }
              
              if (lock.tokens.isEmpty()) {
                  resourceLocks.remove(path);
                  // Removing any lock-null resource which would be present
                  lockNullResources.remove(path);
              }
              
          }
          
          // Checking inheritable collection locks
          
          Enumeration collectionLocksList = collectionLocks.elements();
          while (collectionLocksList.hasMoreElements()) {
              lock = (LockInfo) collectionLocksList.nextElement();
              if (path.equals(lock.path)) {
                  
                  tokenList = lock.tokens.elements();
                  while (tokenList.hasMoreElements()) {
                      String token = (String) tokenList.nextElement();
                      if (lockTokenHeader.indexOf(token) != -1) {
                          lock.tokens.removeElement(token);
                          break;
                      }
                  }
                  
                  if (lock.tokens.isEmpty()) {
                      collectionLocks.removeElement(lock);
                      // Removing any lock-null resource which would be present
                      lockNullResources.remove(path);
                  }
                  
              }
          }
          
          resp.setStatus(WebdavStatus.SC_NO_CONTENT);
          
      }
  
  
      // -------------------------------------------------------- Private Methods
  
  
      /**
       * Generate the namespace declarations.
       */
      private String generateNamespaceDeclarations() {
          return " xmlns=\"" + DEFAULT_NAMESPACE + "\"";
      }
  
  
      /**
       * Check to see if a resource is currently write locked. The method
       * will look at the "If" header to make sure the client
       * has give the appropriate lock tokens.
       * 
       * @param req Servlet request
       * @return boolean true if the resource is locked (and no appropriate
       * lock token has been found for at least one of the non-shared locks which
       * are present on the resource).
       */
      private boolean isLocked(HttpServletRequest req) {
          
          String path = getRelativePath(req);
          
          String ifHeader = req.getHeader("If");
          if (ifHeader == null)
              ifHeader = "";
          
          String lockTokenHeader = req.getHeader("Lock-Token");
          if (lockTokenHeader == null)
              lockTokenHeader = "";
          
          return isLocked(path, ifHeader + lockTokenHeader);
          
      }
  
  
      /**
       * Check to see if a resource is currently write locked.
       * 
       * @param path Path of the resource
       * @param ifHeader "If" HTTP header which was included in the request
       * @return boolean true if the resource is locked (and no appropriate
       * lock token has been found for at least one of the non-shared locks which
       * are present on the resource).
       */
      private boolean isLocked(String path, String ifHeader) {
          
          // Checking resource locks
          
          LockInfo lock = (LockInfo) resourceLocks.get(path);
          Enumeration tokenList = null;
          if ((lock != null) && (lock.hasExpired())) {
              resourceLocks.remove(path);
          } else if (lock != null) {
              
              // At least one of the tokens of the locks must have been given
              
              tokenList = lock.tokens.elements();
              boolean tokenMatch = false;
              while (tokenList.hasMoreElements()) {
                  String token = (String) tokenList.nextElement();
                  if (ifHeader.indexOf(token) != -1)
                      tokenMatch = true;
              }
              if (!tokenMatch)
                  return true;
              
          }
          
          // Checking inheritable collection locks
          
          Enumeration collectionLocksList = collectionLocks.elements();
          while (collectionLocksList.hasMoreElements()) {
              lock = (LockInfo) collectionLocksList.nextElement();
              if (lock.hasExpired()) {
                  collectionLocks.removeElement(lock);
              } else if (path.startsWith(lock.path)) {
                  
                  tokenList = lock.tokens.elements();
                  boolean tokenMatch = false;
                  while (tokenList.hasMoreElements()) {
                      String token = (String) tokenList.nextElement();
                      if (ifHeader.indexOf(token) != -1)
                          tokenMatch = true;
                  }
                  if (!tokenMatch)
                      return true;
                  
              }
          }
          
          return false;
          
      }
  
  
      /**
       * Copy a resource.
       * 
       * @param req Servlet request
       * @param resp Servlet response
       * @return boolean true if the copy is successful
       */
      private boolean copyResource(HttpServletRequest req, 
                                   HttpServletResponse resp)
          throws ServletException, IOException {
          
          // Parsing destination header
          
          String destinationPath = req.getHeader("Destination");
          
          if (destinationPath.startsWith("http://")) {
              destinationPath = destinationPath.substring("http://".length());
          }
          
          String hostName = req.getServerName();
          if ((hostName != null) && (destinationPath.startsWith(hostName))) {
              destinationPath = destinationPath.substring(hostName.length());
          }
          
          if (destinationPath.startsWith(":")) {
              int firstSeparator = destinationPath.indexOf("/");
              if (firstSeparator < 0) {
                  destinationPath = "/";
              } else {
                  destinationPath = destinationPath.substring(firstSeparator);
              }
          }
          
          String contextPath = req.getContextPath();
          if ((contextPath != null) && 
              (destinationPath.startsWith(contextPath))) {
              destinationPath = destinationPath.substring(contextPath.length());
          }
          
          String pathInfo = req.getPathInfo();
          if (pathInfo != null) {
              String servletPath = req.getServletPath();
              if ((servletPath != null) && 
                  (destinationPath.startsWith(servletPath))) {
                  destinationPath = destinationPath
                      .substring(servletPath.length());
              }
          }
          
          if (debug > 0)
              System.out.println("Dest path :" + destinationPath);
          
          String path = getRelativePath(req);
          
          if (destinationPath.equals(path)) {
              resp.sendError(WebdavStatus.SC_FORBIDDEN);
              return false;
          }
          
          // Parsing overwrite header
          
          boolean overwrite = true;
          String overwriteHeader = req.getHeader("Overwrite");
          
          if (overwriteHeader != null) {
              if (overwriteHeader.equalsIgnoreCase("T")) {
                  overwrite = true;
              } else {
                  overwrite = false;
              }
          }
          
          // Overwriting the destination
          
          Resources resources = getResources();
          
          if (overwrite) {
              
              // Delete destination resource, if it exists
              if (resources.exists(destinationPath)) {
                  if (!deleteResource(destinationPath, req, resp)) {
                      return false;
                  } else {
                      resp.setStatus(WebdavStatus.SC_NO_CONTENT);
                  }
              } else {
                  resp.setStatus(WebdavStatus.SC_CREATED);
              }
              
          } else {
              
              // If the destination exists, then it's a conflict
              if (resources.exists(destinationPath)) {
                  resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
                  return false;
              }
              
          }
          
          // Copying source to destination
          
          Hashtable errorList = new Hashtable();
          
          boolean result = copyResource(resources, errorList, 
                                        path, destinationPath);
          
          if ((!result) || (!errorList.isEmpty())) {
              
              sendReport(req, resp, errorList);
              return false;
              
          }
          
          // Removing any lock-null resource which would be present at 
          // the destination path
          lockNullResources.remove(destinationPath);
          
          return true;
          
      }
  
  
      /**
       * Copy a collection.
       * 
       * @param resources Resources implementation to be used
       * @param errorList Hashtable containing the list of errors which occured
       * during the copy operation
       * @param source Path of the resource to be copied
       * @param dest Destination path
       */
      private boolean copyResource(Resources resources, Hashtable errorList,
                                   String source, String dest) {
          
          if (resources.isCollection(source)) {
              
              if (!resources.createCollection(dest)) {
                  errorList.put
                      (dest, 
                       new Integer(WebdavStatus.SC_CONFLICT));
                  return false;
              }
              String[] members = resources.getCollectionMembers(source);
              for (int i=0; i<members.length; i++) {
                  String childDest = dest + 
                      members[i].substring(source.length());
                  copyResource(resources, errorList, members[i], childDest);
              }
              
          } else {
              
              InputStream is = resources.getResourceAsStream(source);
              if (!resources.setResource(dest, is)) {
                  errorList.put
                      (source, 
                       new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
                  return false;
              }
              
          }
          
          return true;
          
      }
  
  
      /**
       * Delete a resource.
       * 
       * @param req Servlet request
       * @param resp Servlet response
       * @return boolean true if the copy is successful
       */
      private boolean deleteResource(HttpServletRequest req, 
                                     HttpServletResponse resp)
          throws ServletException, IOException {
          
          String path = getRelativePath(req);
          
          return deleteResource(path, req, resp);
          
      }
  
  
      /**
       * Delete a resource.
       * 
       * @param path Path of the resource which is to be deleted
       * @param req Servlet request
       * @param resp Servlet response
       */
      private boolean deleteResource(String path, HttpServletRequest req, 
                                     HttpServletResponse resp)
          throws ServletException, IOException {
          
          String ifHeader = req.getHeader("If");
          if (ifHeader == null)
              ifHeader = "";
          
          String lockTokenHeader = req.getHeader("Lock-Token");
          if (lockTokenHeader == null)
              lockTokenHeader = "";
          
          if (isLocked(path, ifHeader + lockTokenHeader)) {
              resp.sendError(WebdavStatus.SC_LOCKED);
              return false;
          }
          
          Resources resources = getResources();
          
  	if (!resources.exists(path)) {
              resp.sendError(WebdavStatus.SC_NOT_FOUND);
  	    return false;
  	}
          
          boolean collection = resources.isCollection(path);
          
          if (!collection) {
              if (!resources.deleteResource(path)) {
                  resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
                  return false;
              }
          } else {
              
              Hashtable errorList = new Hashtable();
              
              deleteCollection(req, resources, path, errorList);
              resources.deleteResource(path);
              
              if (!errorList.isEmpty()) {
                  
                  sendReport(req, resp, errorList);
                  return false;
                  
              }
              
          }
          
          resp.setStatus(WebdavStatus.SC_NO_CONTENT);
          return true;
          
      }
  
  
      /**
       * Deletes a collection.
       * 
       * @param resources Resources implementation associated with the context
       * @param path Path to the collection to be deleted
       * @param errorList Contains the list of the errors which occured
       */
      private void deleteCollection(HttpServletRequest req, Resources resources, 
                                    String path, Hashtable errorList) {
          
          String ifHeader = req.getHeader("If");
          if (ifHeader == null)
              ifHeader = "";
          
          String lockTokenHeader = req.getHeader("Lock-Token");
          if (lockTokenHeader == null)
              lockTokenHeader = "";
          
          String[] members = resources.getCollectionMembers(path);
          
          for (int i=0; i<members.length; i++) {
              
              if (isLocked(members[i], ifHeader + lockTokenHeader)) {
                  
                  errorList.put(members[i], new Integer(WebdavStatus.SC_LOCKED));
                  
              } else {
                  
                  if (resources.isCollection(members[i])) {
                      deleteCollection(req, resources, members[i], errorList);
                  }
                  
                  boolean result = resources.deleteResource(members[i]);
                  if (!result) {
                      if (!resources.isCollection(members[i])) {
                          // If it's not a collection, then it's an unknown
                          // error
                          errorList.put
                              (members[i], new Integer
                                  (WebdavStatus.SC_INTERNAL_SERVER_ERROR));
                      }
                  }
                  
              }
              
          }
          
      }
  
  
      /**
       * Send a multistatus element containing a complete error report to the
       * client.
       * 
       * @param req Servlet request
       * @param resp Servlet response
       * @param errorList List of error to be displayed
       */
      private void sendReport(HttpServletRequest req, HttpServletResponse resp,
                              Hashtable errorList) 
          throws ServletException, IOException {
          
          resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
          
          String absoluteUri = req.getRequestURI();
          String relativePath = getRelativePath(req);
          
          XMLWriter generatedXML = new XMLWriter();
          generatedXML.writeXMLHeader();
          
          generatedXML.writeElement(null, "multistatus" 
                                    + generateNamespaceDeclarations(), 
                                    XMLWriter.OPENING);
          
          Enumeration pathList = errorList.keys();
          while (pathList.hasMoreElements()) {
              
              String errorPath = (String) pathList.nextElement();
              int errorCode = ((Integer) errorList.get(errorPath)).intValue();
              
              generatedXML.writeElement(null, "response", XMLWriter.OPENING);
              
              generatedXML.writeElement(null, "href", XMLWriter.OPENING);
              String toAppend = errorPath.substring(relativePath.length());
              if (!toAppend.startsWith("/"))
                  toAppend = "/" + toAppend;
              generatedXML.writeText(absoluteUri + toAppend);
              generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
              generatedXML.writeElement(null, "status", XMLWriter.OPENING);
              generatedXML
                  .writeText("HTTP/1.1 " + errorCode + " " 
                             + WebdavStatus.getStatusText(errorCode));
              generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
              
              generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
              
          }
          
          generatedXML.writeElement(null, "multistatus", XMLWriter.CLOSING);
          
          Writer writer = resp.getWriter();
          writer.write(generatedXML.toString());
          writer.close();
          
      }
      
  
      /**
       * Propfind helper method.
       * 
       * @param resources Resources object associated with this context
       * @param generatedXML XML response to the Propfind request
       * @param path Path of the current resource
       * @param type Propfind type
       * @param propertiesVector If the propfind type is find properties by 
       * name, then this Vector contains those properties
       */
      private void parseProperties(HttpServletRequest req,
                                   Resources resources, XMLWriter generatedXML,
                                   String path, int type, 
                                   Vector propertiesVector) {
          
  	// Exclude any resource in the /WEB-INF and /META-INF subdirectories
  	// (the "toUpperCase()" avoids problems on Windows systems)
          if (path.toUpperCase().startsWith("/WEB-INF") ||
              path.toUpperCase().startsWith("/META-INF"))
              return;
          
          ResourceInfo resourceInfo = new ResourceInfo(path, resources);
          
          generatedXML.writeElement(null, "response", XMLWriter.OPENING);
          String status = new String("HTTP/1.1 " + WebdavStatus.SC_OK + " " 
                                     + WebdavStatus.getStatusText
                                     (WebdavStatus.SC_OK));
          
          // Generating href element
          generatedXML.writeElement(null, "href", XMLWriter.OPENING);
          
          String absoluteUri = req.getRequestURI();
          String relativePath = getRelativePath(req);
          String toAppend = path.substring(relativePath.length());
          if ((!toAppend.startsWith("/")) && (!absoluteUri.endsWith("/")))
              toAppend = "/" + toAppend;
          
          generatedXML.writeText(absoluteUri + toAppend);
          
          generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
          
          switch (type) {
              
          case FIND_ALL_PROP :
              
              generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
              generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
              
              generatedXML.writeProperty(null, "creationdate", 
                                         formats[0].format(new Date
                                             (resourceInfo.creationDate)));
              generatedXML.writeProperty(null, "displayname", resourceInfo.path);
              generatedXML.writeProperty(null, "getcontentlanguage", 
                                         Locale.getDefault().toString());
              generatedXML.writeProperty(null, "getlastmodified", 
                                         resourceInfo.httpDate);
              if (!resourceInfo.collection) {
                  generatedXML.writeProperty
                      (null, "getcontentlength", 
                       String.valueOf(resourceInfo.length));
                  generatedXML.writeProperty
                      (null, "getcontenttype", 
                       getServletContext().getMimeType(resourceInfo.path));
                  generatedXML.writeProperty(null, "getetag", 
                                             getETagValue(resourceInfo, true));
                  generatedXML.writeElement(null, "resourcetype", 
                                            XMLWriter.NO_CONTENT);
              } else {
                  generatedXML.writeElement(null, "resourcetype", 
                                            XMLWriter.OPENING);
                  generatedXML.writeElement(null, "collection", 
                                            XMLWriter.NO_CONTENT);
                  generatedXML.writeElement(null, "resourcetype", 
                                            XMLWriter.CLOSING);
              }
              
              generatedXML.writeProperty(null, "source", "");
              
              String supportedLocks = "<lockentry>" 
                  + "<lockscope><exclusive/></lockscope>"
                  + "<locktype><write/></locktype>"
                  + "</lockentry>" + "<lockentry>" 
                  + "<lockscope><shared/></lockscope>"
                  + "<locktype><write/></locktype>"
                  + "</lockentry>";
              generatedXML.writeProperty(null, "supportedlock", supportedLocks);
              
              generateLockDiscovery(path, generatedXML);
              
              generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
              generatedXML.writeProperty(null, "status", status);
              generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
              
              break;
              
  	case FIND_PROPERTY_NAMES :
              
              generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
              generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
              
              generatedXML.writeElement(null, "creationdate", 
                                        XMLWriter.NO_CONTENT);
              generatedXML.writeElement(null, "displayname", 
                                        XMLWriter.NO_CONTENT);
              if (!resourceInfo.collection) {
                  generatedXML.writeElement(null, "getcontentlanguage",
                                            XMLWriter.NO_CONTENT);
                  generatedXML.writeElement(null, "getcontentlength", 
                                            XMLWriter.NO_CONTENT);
                  generatedXML.writeElement(null, "getcontenttype", 
                                            XMLWriter.NO_CONTENT);
                  generatedXML.writeElement(null, "getetag", 
                                            XMLWriter.NO_CONTENT);
                  generatedXML.writeElement(null, "getlastmodified", 
                                            XMLWriter.NO_CONTENT);
              }
              generatedXML.writeElement(null, "resourcetype", 
                                        XMLWriter.NO_CONTENT);
              generatedXML.writeElement(null, "source", XMLWriter.NO_CONTENT);
              generatedXML.writeElement(null, "lockdiscovery", 
                                        XMLWriter.NO_CONTENT);
              
              generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
              generatedXML.writeProperty(null, "status", status);
              generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
              
              break;
              
          case FIND_BY_PROPERTY :
              
              Vector propertiesNotFound = new Vector();
              
              // Parse the list of properties
              
              generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
              generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
              
              Enumeration properties = propertiesVector.elements();
              
              while (properties.hasMoreElements()) {
                  
                  String property = (String) properties.nextElement();
                  
                  if (property.equals("creationdate")) {
                      generatedXML.writeProperty
                          (null, "creationdate", formats[0].format
                           (new Date(resourceInfo.creationDate)));
                  } else if (property.equals("displayname")) {
                      generatedXML.writeProperty(null, "displayname", 
                                                 resourceInfo.path);
                  } else if (property.equals("getcontentlanguage")) {
                      if (resourceInfo.collection) {
                          propertiesNotFound.addElement(property);
                      } else {
                          generatedXML.writeProperty
                              (null, "getcontentlanguage", 
                               Locale.getDefault().toString());
                      }
                  } else if (property.equals("getcontentlength")) {
                      if (resourceInfo.collection) {
                          propertiesNotFound.addElement(property);
                      } else {
                          generatedXML.writeProperty
                              (null, "getcontentlength", 
                               (String.valueOf(resourceInfo.length)));
                      }
                  } else if (property.equals("getcontenttype")) {
                      if (resourceInfo.collection) {
                          propertiesNotFound.addElement(property);
                      } else {
                          generatedXML.writeProperty
                              (null, "getcontenttype", 
                               getServletContext().getMimeType
                               (resourceInfo.path));
                      }
                  } else if (property.equals("getetag")) {
                      if (resourceInfo.collection) {
                          propertiesNotFound.addElement(property);
                      } else {
                          generatedXML.writeProperty
                              (null, "getetag", 
                               getETagValue(resourceInfo, true));
                      }
                  } else if (property.equals("getlastmodified")) {
                      if (resourceInfo.collection) {
                          propertiesNotFound.addElement(property);
                      } else {
                          generatedXML.writeProperty(null, 
                                                     "getlastmodified", 
                                                     resourceInfo.httpDate);
                      }
                  } else if (property.equals("resourcetype")) {
                      if (resourceInfo.collection) {
                          generatedXML.writeElement(null, "resourcetype", 
                                                    XMLWriter.OPENING);
                          generatedXML.writeElement(null, "collection", 
                                                    XMLWriter.NO_CONTENT);
                          generatedXML.writeElement(null, "resourcetype", 
                                                    XMLWriter.CLOSING);
                      } else {
                          generatedXML.writeElement(null, "resourcetype", 
                                                    XMLWriter.NO_CONTENT);
                      }
                  } else if (property.equals("source")) {
                      generatedXML.writeProperty(null, "source", "");
                  } else if (property.equals("supportedlock")) {
                      supportedLocks = "<lockentry>" 
                          + "<lockscope><exclusive/></lockscope>"
                          + "<locktype><write/></locktype>"
                          + "</lockentry>" + "<lockentry>" 
                          + "<lockscope><shared/></lockscope>"
                          + "<locktype><write/></locktype>"
                          + "</lockentry>";
                      generatedXML.writeProperty(null, "supportedlock",
                                                 supportedLocks);
                  } else if (property.equals("lockdiscovery")) {
                      generateLockDiscovery(path, generatedXML);
                  } else {
                      propertiesNotFound.addElement(property);
                  }
                  
              }
              
              generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
              generatedXML.writeProperty(null, "status", status);
              generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
              
              Enumeration propertiesNotFoundList = propertiesNotFound.elements();
              
              if (propertiesNotFoundList.hasMoreElements()) {
                  
                  status = new String("HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND 
                                      + " " + WebdavStatus.getStatusText
                                      (WebdavStatus.SC_NOT_FOUND));
                  
                  generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
                  generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
                  
                  while (propertiesNotFoundList.hasMoreElements()) {
                      generatedXML.writeElement
                          (null, (String) propertiesNotFoundList.nextElement(), 
                           XMLWriter.NO_CONTENT);
                  }
                  
                  generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
                  generatedXML.writeProperty(null, "status", status);
                  generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
                  
              }
              
              break;
              
          }
          
          generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
          
      }
  
  
      /**
       * Propfind helper method. Dispays the properties of a lock-null resource.
       * 
       * @param resources Resources object associated with this context
       * @param generatedXML XML response to the Propfind request
       * @param path Path of the current resource
       * @param type Propfind type
       * @param propertiesVector If the propfind type is find properties by 
       * name, then this Vector contains those properties
       */
      private void parseLockNullProperties(HttpServletRequest req,
                                           XMLWriter generatedXML,
                                           String path, int type, 
                                           Vector propertiesVector) {
          
  	// Exclude any resource in the /WEB-INF and /META-INF subdirectories
  	// (the "toUpperCase()" avoids problems on Windows systems)
          if (path.toUpperCase().startsWith("/WEB-INF") ||
              path.toUpperCase().startsWith("/META-INF"))
              return;
          
          // Retrieving the lock associated with the lock-null resource
          LockInfo lock = (LockInfo) resourceLocks.get(path);
          
          if (lock == null)
              return;
          
          generatedXML.writeElement(null, "response", XMLWriter.OPENING);
          String status = new String("HTTP/1.1 " + WebdavStatus.SC_OK + " " 
                                     + WebdavStatus.getStatusText
                                     (WebdavStatus.SC_OK));
          
          // Generating href element
          generatedXML.writeElement(null, "href", XMLWriter.OPENING);
          
          String absoluteUri = req.getRequestURI();
          String relativePath = getRelativePath(req);
          String toAppend = path.substring(relativePath.length());
          if (!toAppend.startsWith("/"))
              toAppend = "/" + toAppend;
          
          generatedXML.writeText(absoluteUri + toAppend);
          
          generatedXML.writeElement("d", "href", XMLWriter.CLOSING);
          
          switch (type) {
              
          case FIND_ALL_PROP :
              
              generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
              generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
              
              generatedXML.writeProperty(null, "creationdate", 
                                         formats[0].format(lock.creationDate));
              generatedXML.writeProperty(null, "displayname", path);
              generatedXML.writeProperty(null, "getcontentlanguage", 
                                         Locale.getDefault().toString());
              generatedXML.writeProperty(null, "getlastmodified", 
                                         formats[0].format(lock.creationDate));
              generatedXML.writeProperty
                  (null, "getcontentlength", String.valueOf(0));
              generatedXML.writeProperty(null, "getcontenttype", "");
              generatedXML.writeProperty(null, "getetag", "");
              generatedXML.writeElement(null, "resourcetype", 
                                        XMLWriter.OPENING);
              generatedXML.writeElement(null, "lock-null", XMLWriter.NO_CONTENT);
              generatedXML.writeElement(null, "resourcetype", 
                                        XMLWriter.CLOSING);
              
              generatedXML.writeProperty(null, "source", "");
              
              String supportedLocks = "<lockentry>" 
                  + "<lockscope><exclusive/></lockscope>"
                  + "<locktype><write/></locktype>"
                  + "</lockentry>" + "<lockentry>" 
                  + "<lockscope><shared/></lockscope>"
                  + "<locktype><write/></locktype>"
                  + "</lockentry>";
              generatedXML.writeProperty(null, "supportedlock", 
                                         supportedLocks);
              
              generateLockDiscovery(path, generatedXML);
              
              generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
              generatedXML.writeProperty(null, "status", status);
              generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
              
              break;
              
  	case FIND_PROPERTY_NAMES :
              
              generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
              generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
              
              generatedXML.writeElement(null, "creationdate", 
                                        XMLWriter.NO_CONTENT);
              generatedXML.writeElement(null, "displayname", 
                                        XMLWriter.NO_CONTENT);
              generatedXML.writeElement(null, "getcontentlanguage",
                                        XMLWriter.NO_CONTENT);
              generatedXML.writeElement(null, "getcontentlength", 
                                        XMLWriter.NO_CONTENT);
              generatedXML.writeElement(null, "getcontenttype", 
                                        XMLWriter.NO_CONTENT);
              generatedXML.writeElement(null, "getetag", 
                                        XMLWriter.NO_CONTENT);
              generatedXML.writeElement(null, "getlastmodified", 
                                        XMLWriter.NO_CONTENT);
              generatedXML.writeElement(null, "resourcetype", 
                                        XMLWriter.NO_CONTENT);
              generatedXML.writeElement(null, "source", 
                                        XMLWriter.NO_CONTENT);
              generatedXML.writeElement(null, "lockdiscovery", 
                                        XMLWriter.NO_CONTENT);
              
              generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
              generatedXML.writeProperty(null, "status", status);
              generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
              
              break;
              
          case FIND_BY_PROPERTY :
              
              Vector propertiesNotFound = new Vector();
              
              // Parse the list of properties
              
              generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
              generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
              
              Enumeration properties = propertiesVector.elements();
              
              while (properties.hasMoreElements()) {
                  
                  String property = (String) properties.nextElement();
                  
                  if (property.equals("creationdate")) {
                      generatedXML.writeProperty
                          (null, "creationdate", 
                           formats[0].format(lock.creationDate));
                  } else if (property.equals("displayname")) {
                      generatedXML.writeProperty(null, "displayname", path);
                  } else if (property.equals("getcontentlanguage")) {
                      generatedXML.writeProperty
                          (null, "getcontentlanguage", 
                           Locale.getDefault().toString());
                  } else if (property.equals("getcontentlength")) {
                      generatedXML.writeProperty
                          (null, "getcontentlength", (String.valueOf(0)));
                  } else if (property.equals("getcontenttype")) {
                      generatedXML.writeProperty
                          (null, "getcontenttype", "");
                  } else if (property.equals("getetag")) {
                      generatedXML.writeProperty(null, "getetag", "");
                  } else if (property.equals("getlastmodified")) {
                      generatedXML.writeProperty
                          (null, "getlastmodified", 
                           formats[0].format(lock.creationDate));
                  } else if (property.equals("resourcetype")) {
                      generatedXML.writeElement(null, "resourcetype", 
                                                XMLWriter.OPENING);
                      generatedXML.writeElement(null, "lock-null", 
                                                XMLWriter.NO_CONTENT);
                      generatedXML.writeElement(null, "resourcetype", 
                                                XMLWriter.CLOSING);
                  } else if (property.equals("source")) {
                      generatedXML.writeProperty(null, "source", "");
                  } else if (property.equals("supportedlock")) {
                      supportedLocks = "<lockentry>" 
                          + "<lockscope><exclusive/></lockscope>"
                          + "<locktype><write/></locktype>"
                          + "</lockentry>" + "<lockentry>" 
                          + "<lockscope><shared/></lockscope>"
                          + "<locktype><write/></locktype>"
                          + "</lockentry>";
                      generatedXML.writeProperty(null, "supportedlock",
                                                 supportedLocks);
                  } else if (property.equals("lockdiscovery")) {
                      generateLockDiscovery(path, generatedXML);
                  } else {
                      propertiesNotFound.addElement(property);
                  }
                  
              }
              
              generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
              generatedXML.writeProperty(null, "status", status);
              generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
              
              Enumeration propertiesNotFoundList = propertiesNotFound.elements();
              
              if (propertiesNotFoundList.hasMoreElements()) {
                  
                  status = new String("HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND 
                                      + " " + WebdavStatus.getStatusText
                                      (WebdavStatus.SC_NOT_FOUND));
                  
                  generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
                  generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
                  
                  while (propertiesNotFoundList.hasMoreElements()) {
                      generatedXML.writeElement
                          (null, (String) propertiesNotFoundList.nextElement(), 
                           XMLWriter.NO_CONTENT);
                  }
                  
                  generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
                  generatedXML.writeProperty(null, "status", status);
                  generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
                  
              }
              
              break;
              
          }
          
          generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
          
      }
  
  
      /**
       * Print the lock discovery information associated with a path.
       * 
       * @param path Path
       * @param generatedXML XML data to which the locks info will be appended
       */
      private void generateLockDiscovery(String path, XMLWriter generatedXML) {
          
          generatedXML.writeElement(null, "lockdiscovery", 
                                    XMLWriter.OPENING);
          
          LockInfo resourceLock = (LockInfo) resourceLocks.get(path);
          if (resourceLock != null) {
              resourceLock.toXML(generatedXML);
          }
          
          Enumeration collectionLocksList = collectionLocks.elements();
          while (collectionLocksList.hasMoreElements()) {
              LockInfo currentLock = (LockInfo) collectionLocksList.nextElement();
              if (path.startsWith(currentLock.path)) {
                  currentLock.toXML(generatedXML);
              }
          }
          
          generatedXML.writeElement(null, "lockdiscovery", 
                                    XMLWriter.CLOSING);
          
      }
  
  
      // --------------------------------------------------  LockInfo Inner Class
  
  
      /**
       * Holds a lock information.
       */
      private class LockInfo {
  
  
          // --------------------------------------------------------- Constructor
  
  
          /**
           * Constructor.
           * 
           * @param pathname Path name of the file
           */
          public LockInfo() {
  
          }
  
  
          // -------------------------------------------------- Instance Variables
  
  
          String path = "/";
          String type = "write";
          String scope = "exclusive";
          int depth = 0;
          String owner = "";
          Vector tokens = new Vector();
          long expiresAt = 0;
          Date creationDate = new Date();
          
          
          // ------------------------------------------------------ Public Methods
  
  
          /**
           * Get a String representation of this lock token.
           */
          public String toString() {
              
              String result =  "Type:" + type + "\n";
              result += "Scope:" + scope + "\n";
              result += "Depth:" + depth + "\n";
              result += "Owner:" + owner + "\n";
              result += "Expiration:" + 
                  formats[0].format(new Date(expiresAt)) + "\n";
              Enumeration tokensList = tokens.elements();
              while (tokensList.hasMoreElements()) {
                  result += "Token:" + tokensList.nextElement() + "\n";
              }
              return result;
              
          }
          
          
          /**
           * Return true if the lock has expired.
           */
          public boolean hasExpired() {
              return (System.currentTimeMillis() > expiresAt);
          }
          
          
          /**
           * Return true if the lock is exclusive.
           */
          public boolean isExclusive() {
              
              return (scope.equals("exclusive"));
              
          }
          
          
          /**
           * Get an XML representation of this lock token. This method will
           * append an XML fragment to the given XML writer.
           */
          public void toXML(XMLWriter generatedXML) {
              toXML(generatedXML, false);
          }
          
          
          /**
           * Get an XML representation of this lock token. This method will
           * append an XML fragment to the given XML writer.
           */
          public void toXML(XMLWriter generatedXML, boolean showToken) {
              
              generatedXML.writeElement(null, "activelock", XMLWriter.OPENING);
              
              generatedXML.writeElement(null, "locktype", XMLWriter.OPENING);
              generatedXML.writeElement(null, type, XMLWriter.NO_CONTENT);
              generatedXML.writeElement(null, "locktype", XMLWriter.CLOSING);
              
              generatedXML.writeElement(null, "lockscope", XMLWriter.OPENING);
              generatedXML.writeElement(null, scope, XMLWriter.NO_CONTENT);
              generatedXML.writeElement(null, "lockscope", XMLWriter.CLOSING);
              
              generatedXML.writeElement(null, "depth", XMLWriter.OPENING);
              if (depth == INFINITY) {
                  generatedXML.writeText("Infinity");
              } else {
                  generatedXML.writeText("0");
              }
              generatedXML.writeElement(null, "depth", XMLWriter.CLOSING);
              
              generatedXML.writeElement(null, "owner", XMLWriter.OPENING);
              generatedXML.writeText(owner);
              generatedXML.writeElement(null, "owner", XMLWriter.CLOSING);
              
              generatedXML.writeElement(null, "timeout", XMLWriter.OPENING);
              long timeout = (expiresAt - System.currentTimeMillis()) / 1000;
              generatedXML.writeText("Second-" + timeout);
              generatedXML.writeElement(null, "timeout", XMLWriter.CLOSING);
              
              generatedXML.writeElement(null, "locktoken", XMLWriter.OPENING);
              if (showToken) {
                  Enumeration tokensList = tokens.elements();
                  while (tokensList.hasMoreElements()) {
                      generatedXML.writeElement(null, "href", XMLWriter.OPENING);
                      generatedXML.writeText("opaquelocktoken:" 
                                             + tokensList.nextElement());
                      generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
                  }
              } else {
                  generatedXML.writeElement(null, "href", XMLWriter.OPENING);
                  generatedXML.writeText("opaquelocktoken:dummytoken");
                  generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
              }
              generatedXML.writeElement(null, "locktoken", XMLWriter.CLOSING);
              
              generatedXML.writeElement(null, "activelock", XMLWriter.CLOSING);
              
          }
          
          
      }
  
  
      // --------------------------------------------------- Property Inner Class
      
      
      private class Property {
          
          public String name;
          public String value;
          public String namespace;
          public String namespaceAbbrev;
          public int status = WebdavStatus.SC_OK;
          
      }
  
  
  };
  
  
  // --------------------------------------------------------  WebdavStatus Class
  
  
  /**
   * Wraps the HttpServletResponse class to abstract the
   * specific protocol used.  To support other protocols
   * we would only need to modify this class and the
   * WebDavRetCode classes.
   *
   * @author		Marc Eaddy
   * @version		1.0, 16 Nov 1997
   */
  class WebdavStatus {
  
      
      // ----------------------------------------------------- Instance Variables
  
  
      /**
       * This Hashtable contains the mapping of HTTP and WebDAV
       * status codes to descriptive text.  This is a static
       * variable.
       */
      private static Hashtable mapStatusCodes = new Hashtable();
  
      
      // ------------------------------------------------------ HTTP Status Codes
  
  
      /**
       * Status code (200) indicating the request succeeded normally.
       */
      public static final int SC_OK = HttpServletResponse.SC_OK;
  
      
      /**
       * Status code (201) indicating the request succeeded and created
       * a new resource on the server.
       */
      public static final int SC_CREATED = HttpServletResponse.SC_CREATED;
  
  
      /**
       * Status code (202) indicating that a request was accepted for
       * processing, but was not completed.
       */
      public static final int SC_ACCEPTED = HttpServletResponse.SC_ACCEPTED;
  
  
      /**
       * Status code (204) indicating that the request succeeded but that
       * there was no new information to return.
       */
      public static final int SC_NO_CONTENT = HttpServletResponse.SC_NO_CONTENT;
  
  
      /**
       * Status code (301) indicating that the resource has permanently
       * moved to a new location, and that future references should use a
       * new URI with their requests.
       */
      public static final int SC_MOVED_PERMANENTLY = 
          HttpServletResponse.SC_MOVED_PERMANENTLY;
  
  
      /**
       * Status code (302) indicating that the resource has temporarily
       * moved to another location, but that future references should
       * still use the original URI to access the resource.
       */
      public static final int SC_MOVED_TEMPORARILY = 
          HttpServletResponse.SC_MOVED_TEMPORARILY;
  
  
      /**
       * Status code (304) indicating that a conditional GET operation
       * found that the resource was available and not modified.
       */
      public static final int SC_NOT_MODIFIED = 
          HttpServletResponse.SC_NOT_MODIFIED;
  
  
      /**
       * Status code (400) indicating the request sent by the client was
       * syntactically incorrect.
       */
      public static final int SC_BAD_REQUEST = 
          HttpServletResponse.SC_BAD_REQUEST;
  
  
      /**
       * Status code (401) indicating that the request requires HTTP
       * authentication.
       */
      public static final int SC_UNAUTHORIZED = 
          HttpServletResponse.SC_UNAUTHORIZED;
  
  
      /**
       * Status code (403) indicating the server understood the request
       * but refused to fulfill it.
       */
      public static final int SC_FORBIDDEN = HttpServletResponse.SC_FORBIDDEN;
  
  
      /**
       * Status code (404) indicating that the requested resource is not
       * available.
       */
      public static final int SC_NOT_FOUND = HttpServletResponse.SC_NOT_FOUND;
  
  
      /**
       * Status code (500) indicating an error inside the HTTP service
       * which prevented it from fulfilling the request.
       */
      public static final int SC_INTERNAL_SERVER_ERROR = 
          HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
  
  
      /**
       * Status code (501) indicating the HTTP service does not support
       * the functionality needed to fulfill the request.
       */
      public static final int SC_NOT_IMPLEMENTED = 
          HttpServletResponse.SC_NOT_IMPLEMENTED;
  
  
      /**
       * Status code (502) indicating that the HTTP server received an
       * invalid response from a server it consulted when acting as a
       * proxy or gateway.
       */
      public static final int SC_BAD_GATEWAY = 
          HttpServletResponse.SC_BAD_GATEWAY;
  
  
      /**
       * Status code (503) indicating that the HTTP service is
       * temporarily overloaded, and unable to handle the request.
       */
      public static final int SC_SERVICE_UNAVAILABLE = 
          HttpServletResponse.SC_SERVICE_UNAVAILABLE;
  
  
      /**
       * Status code (100) indicating the client may continue with
       * its request.  This interim response is used to inform the 
       * client that the initial part of the request has been
       * received and has not yet been rejected by the server.
       */
      public static final int SC_CONTINUE = 100;
  
  
      /**
       * Status code (405) indicating the method specified is not
       * allowed for the resource.
       */
      public static final int SC_METHOD_NOT_ALLOWED = 405;
  
      
      /**
       * Status code (409) indicating that the request could not be
       * completed due to a conflict with the current state of the
       * resource.
       */
      public static final int SC_CONFLICT	= 409;
  
  
      /**
       * Status code (412) indicating the precondition given in one
       * or more of the request-header fields evaluated to false
       * when it was tested on the server.
       */
      public static final int SC_PRECONDITION_FAILED = 412;
  
  
      /**
       * Status code (413) indicating the server is refusing to
       * process a request because the request entity is larger
       * than the server is willing or able to process.
       */
      public static final int SC_REQUEST_TOO_LONG	= 413;
  
  
      /**
       * Status code (415) indicating the server is refusing to service
       * the request because the entity of the request is in a format
       * not supported by the requested resource for the requested
       * method.
       */
      public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415;
      
      
      // -------------------------------------------- Extended WebDav status code
  
  
      /**
       * Status code (207) indicating that the response requires
       * providing status for multiple independent operations.
       */
      public static final int SC_MULTI_STATUS = 207;
      // This one colides with HTTP 1.1
      // "207 Parital Update OK"
  
      
      /**
       * Status code (418) indicating the entity body submitted with
       * the PATCH method was not understood by the resource.
       */
      public static final int SC_UNPROCESSABLE_ENTITY = 418;
      // This one colides with HTTP 1.1
      // "418 Reauthentication Required"
  
      
      /**
       * Status code (419) indicating that the resource does not have
       * sufficient space to record the state of the resource after the
       * execution of this method.
       */
      public static final int SC_INSUFFICIENT_SPACE_ON_RESOURCE = 419;
      // This one colides with HTTP 1.1
      // "419 Proxy Reauthentication Required"
  
  
      /**
       * Status code (420) indicating the method was not executed on
       * a particular resource within its scope because some part of
       * the method's execution failed causing the entire method to be
       * aborted.
       */
      public static final int SC_METHOD_FAILURE = 420;
  
      
      /**
       * Status code (423) indicating the destination resource of a
       * method is locked, and either the request did not contain a
       * valid Lock-Info header, or the Lock-Info header identifies
       * a lock held by another principal.
       */
      public static final int SC_LOCKED = 423;
  
      
      // ------------------------------------------------------------ Initializer
  
  
      static {
  	// HTTP 1.0 tatus Code
  	addStatusCodeMap(SC_OK, "OK");
  	addStatusCodeMap(SC_CREATED, "Created");
  	addStatusCodeMap(SC_ACCEPTED, "Accepted");
  	addStatusCodeMap(SC_NO_CONTENT, "No Content");
  	addStatusCodeMap(SC_MOVED_PERMANENTLY, "Moved Permanently");
  	addStatusCodeMap(SC_MOVED_TEMPORARILY, "Moved Temporarily");
  	addStatusCodeMap(SC_NOT_MODIFIED, "Not Modified");
  	addStatusCodeMap(SC_BAD_REQUEST, "Bad Request");
  	addStatusCodeMap(SC_UNAUTHORIZED, "Unauthorized");
  	addStatusCodeMap(SC_FORBIDDEN, "Forbidden");
  	addStatusCodeMap(SC_NOT_FOUND, "Not Found");
  	addStatusCodeMap(SC_INTERNAL_SERVER_ERROR, "Internal Server Error");
  	addStatusCodeMap(SC_NOT_IMPLEMENTED, "Not Implemented");
  	addStatusCodeMap(SC_BAD_GATEWAY, "Bad Gateway");
  	addStatusCodeMap(SC_SERVICE_UNAVAILABLE, "Service Unavailable");
  	addStatusCodeMap(SC_CONTINUE, "Continue");
  	addStatusCodeMap(SC_METHOD_NOT_ALLOWED, "Method Not Allowed");
  	addStatusCodeMap(SC_CONFLICT, "Conflict");
  	addStatusCodeMap(SC_PRECONDITION_FAILED, "Precondition Failed");
  	addStatusCodeMap(SC_REQUEST_TOO_LONG, "Request Too Long");
  	addStatusCodeMap(SC_UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type");
  	// WebDav Status Codes
  	addStatusCodeMap(SC_MULTI_STATUS, "Multi-Status");
  	addStatusCodeMap(SC_UNPROCESSABLE_ENTITY, "Unprocessable Entity");
  	addStatusCodeMap(SC_INSUFFICIENT_SPACE_ON_RESOURCE, 
                           "Insufficient Space On Resource");
  	addStatusCodeMap(SC_METHOD_FAILURE, "Method Failure");
  	addStatusCodeMap(SC_LOCKED, "Locked");
      }
      
  
      // --------------------------------------------------------- Public Methods
  
  
      /**
       * Returns the HTTP status text for the HTTP or WebDav status code
       * specified by looking it up in the static mapping.  This is a
       * static function.
       * 
       * @param	nHttpStatusCode	[IN] HTTP or WebDAV status code
       * @return	A string with a short descriptive phrase for the
       *			HTTP status code (e.g., "OK").
       */
      public static String getStatusText(int nHttpStatusCode) {
  	Integer intKey = new Integer(nHttpStatusCode);
  	
  	if (!mapStatusCodes.containsKey(intKey)) {
  	    return "";
  	} else {
  	    return (String) mapStatusCodes.get(intKey);
  	}
      }
  
      
      // -------------------------------------------------------- Private Methods
  
  
      /**
       * Adds a new status code -> status text mapping.  This is a static
       * method because the mapping is a static variable.
       * 
       * @param	nKey	[IN] HTTP or WebDAV status code
       * @param	strVal	[IN] HTTP status text
       */
      private static void addStatusCodeMap(int nKey, String strVal) {
  	mapStatusCodes.put(new Integer(nKey), strVal);
      }
  
      
  };
  
  
  
  
  1.1                  jakarta-tomcat/src/webdav/org/apache/tomcat/webdav/resources/DirectoryBean.java
  
  Index: DirectoryBean.java
  ===================================================================
  /*
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */
  
  
  package org.apache.tomcat.webdav.resources;
  
  
  import java.io.ByteArrayInputStream;
  import java.io.ByteArrayOutputStream;
  import java.io.File;
  import java.io.InputStream;
  import java.io.PrintWriter;
  import java.text.DateFormat;
  import java.text.SimpleDateFormat;
  import java.util.Date;
  import java.util.Locale;
  import java.util.TreeMap;
  import java.util.jar.JarEntry;
  
  import org.apache.tomcat.webdav.util.StringManager;
  
  
  /**
   * Abstraction bean that represents the properties of a "resource" that is
   * actually a "directory", in a fashion that independent of the actual
   * underlying medium used to represent those entries.  Convenient constructors
   * are provided to populate our properties from common sources, but it is
   * feasible to do everything with property setters if necessary.
   * <p>
   * <strong>IMPLEMENTATION NOTE</strong>:  It is assumed that access to the
   * set of resources associated with this directory are done in a thread safe
   * manner.  No internal synchronization is performed.
   *
   * @author Craig R. McClanahan
   * @version $Revision: 1.1 $ $Date: 2000/11/03 21:27:40 $
   */
  
  public final class DirectoryBean extends ResourceBean {
  
      // ----------------------------------------------------------- Constructors
  
  
      /**
       * Construct a new directory bean for the named resource, with default
       * properties.
       *
       * @param name Normalized context-relative name of this resource
       */
      public DirectoryBean(String name) {
  
  	super(name);
  
      }
  
  
      /**
       * Construct a new directory bean for the named resource, with properties
       * populated from the specified object.  Note that the data content of
       * this resource is <strong>not</strong> initialized unless and until
       * <code>setData()</code> is called.
       *
       * @param name Normalized context-relative name of this resource
       * @param file File representing this resource entry
       */
      public DirectoryBean(String name, File file) {
  
  	super(name, file);
  
      }
  
  
      /**
       * Construct a new directory bean for the named resource, with properties
       * populated from the specified object.  Note that the data content of
       * this resource is <strong>not</strong> initialized unless and until
       * <code>setData()</code> is called.
       *
       * @param name Normalized context-relative name of this resource
       * @param entry JAR entry representing this resource entry
       */
      public DirectoryBean(String name, JarEntry entry) {
  
  	super(name, entry);
  
      }
  
  
      // ----------------------------------------------------- Instance Variables
  
  
      /**
       * The system default Locale.
       */
      private static Locale defaultLocale = Locale.getDefault();
  
  
      /**
       * The date format pattern for rendering last modified date and time.
       */
      private static String pattern = "EEE, dd MMM yyyy HH:mm z";
  
  
      /**
       * The collection of resources for this directory, sorted by name.
       */
      private TreeMap resources = new TreeMap();
  
  
      /**
       * The string manager for this package.
       */
      protected static final StringManager sm =
  	StringManager.getManager("org.apache.tomcat.webdav.resources");
  
  
      /**
       * The date format for rendering last modified date and time.
       */
      private static DateFormat timestamp =
  	new SimpleDateFormat(pattern, defaultLocale);
  
  
      // --------------------------------------------------------- Public Methods
  
  
      /**
       * Add a new resource to this directory.
       *
       * @param entry ResourceBean for the resource to be added
       */
      public void addResource(ResourceBean resource) {
  
  	resources.put(resource.getName(), resource);
  
      }
  
  
      /**
       * Return the set of resources that belong to this directory,
       * in alphabetical order based on their names.
       */
      public ResourceBean[] findResources() {
  
  	ResourceBean results[] = new ResourceBean[resources.size()];
  	return ((ResourceBean[]) resources.values().toArray(results));
  
      }
  
  
      /**
       * Remove an existing resource from this directory.
       *
       * @param entry ResourceBean for the resource to be removed
       */
      public void removeResource(ResourceBean resource) {
  
  	resources.remove(resource.getName());
  
      }
  
  
      /**
       * Return an InputStream to an HTML representation of the contents
       * of this directory.
       *
       * @param contextPath Context path to which our internal paths are
       *  relative
       */
      public InputStream render(String contextPath, String serverInfo) {
  
  	// Number of characters to trim from the beginnings of filenames
  	int trim = name.length();
  	if (!name.endsWith("/"))
  	    trim += 1;
  	if (name.equals("/"))
  	    trim = 1;
  
  	// Prepare a writer to a buffered area
  	ByteArrayOutputStream stream = new ByteArrayOutputStream();
  	PrintWriter writer = new PrintWriter(stream);
  
  	// FIXME - Currently pays no attention to the user's Locale
  
  	// Render the page header
  	writer.print("<html>\r\n");
  	writer.print("<head>\r\n");
  	writer.print("<title>");
  	writer.print(sm.getString("directory.title", name));
  	writer.print("</title>\r\n</head>\r\n");
  	writer.print("<body bgcolor=\"white\">\r\n");
  	writer.print("<table width=\"90%\" cellspacing=\"0\"" +
  		     " cellpadding=\"5\" align=\"center\">\r\n");
  
  	// Render the in-page title
  	writer.print("<tr><td colspan=\"3\"><font size=\"+2\">\r\n<strong>");
  	writer.print(sm.getString("directory.title", name));
  	writer.print("</strong>\r\n</font></td></tr>\r\n");
  
  	// Render the link to our parent (if required)
          String parentDirectory = name;
          if (parentDirectory.endsWith("/")) {
              parentDirectory = 
                  parentDirectory.substring(0, parentDirectory.length() - 1);
          }
  	int slash = parentDirectory.lastIndexOf("/");
  	if (slash >= 0) {
  	    String parent = name.substring(0, slash);
  	    writer.print("<tr><td colspan=\"3\" bgcolor=\"#ffffff\">\r\n");
  	    writer.print("<a href=\"");
  	    writer.print(rewriteUrl(contextPath));
              if (parent.equals(""))
                  parent = "/";
  	    writer.print(parent);
  	    writer.print("\">");
  	    writer.print(sm.getString("directory.parent", parent));
  	    writer.print("</a>\r\n");
  	    writer.print("</td></tr>\r\n");
  	}
  
  	// Render the column headings
  	writer.print("<tr bgcolor=\"#cccccc\">\r\n");
  	writer.print("<td align=\"left\"><font size=\"+1\"><strong>");
  	writer.print(sm.getString("directory.filename"));
  	writer.print("</strong></font></td>\r\n");
  	writer.print("<td align=\"center\"><font size=\"+1\"><strong>");
  	writer.print(sm.getString("directory.size"));
  	writer.print("</strong></font></td>\r\n");
  	writer.print("<td align=\"right\"><font size=\"+1\"><strong>");
  	writer.print(sm.getString("directory.lastModified"));
  	writer.print("</strong></font></td>\r\n");
  	writer.print("</tr>\r\n");
  
  	// Render the directory entries within this directory
  	ResourceBean resources[] = findResources();
  	boolean shade = false;
  	for (int i = 0;i < resources.length; i++) {
  
  	    String trimmed = resources[i].getName().substring(trim);
  	    if (trimmed.equalsIgnoreCase("WEB-INF") ||
  		trimmed.equalsIgnoreCase("META-INF"))
  		continue;
  
  	    writer.print("<tr");
  	    if (shade)
  		writer.print(" bgcolor=\"eeeeee\"");
  	    writer.print(">\r\n");
  	    shade = !shade;
  
  	    writer.print("<td align=\"left\">&nbsp;&nbsp;\r\n");
  	    writer.print("<a href=\"");
  	    writer.print(rewriteUrl(contextPath));
  	    writer.print(rewriteUrl(resources[i].getName()));
  	    writer.print("\"><tt>");
  	    writer.print(trimmed);
  	    if (resources[i] instanceof DirectoryBean)
  		writer.print("/");
  	    writer.print("</tt></a></td>\r\n");
  
  	    writer.print("<td align=\"right\"><tt>");
  	    if (resources[i] instanceof DirectoryBean)
  		writer.print("&nbsp;");
  	    else
  		writer.print(renderSize(resources[i].getSize()));
  	    writer.print("</tt></td>\r\n");
  
  	    writer.print("<td align=\"right\"><tt>");
  	    writer.print(renderLastModified(resources[i].getLastModified()));
  	    writer.print("</tt></td>\r\n");
  
  	    writer.print("</tr>\r\n");
  	}
  
  	// Render the page footer
  	writer.print("<tr><td colspan=\"3\">&nbsp;</td></tr>\r\n");
  	writer.print("<tr><td colspan=\"3\" bgcolor=\"#cccccc\">");
  	writer.print("<font size=\"-1\">");
  	writer.print(serverInfo);
  	writer.print("</font></td></tr>\r\n");
  	writer.print("</table>\r\n");
  	writer.print("</body>\r\n");
  	writer.print("</html>\r\n");
  
  	// Return an input stream to the underlying bytes
  	writer.flush();
  	return (new ByteArrayInputStream(stream.toByteArray()));
  
      }
  
  
      /**
       * Render the last modified date and time for the specified timestamp.
       *
       * @param lastModified Last modified date and time, in milliseconds since
       *  the epoch
       */
      private String renderLastModified(long lastModified) {
  
  	return (timestamp.format(new Date(lastModified)));
  
      }
  
  
      /**
       * Render the specified file size (in bytes).
       *
       * @param size File size (in bytes)
       */
      private String renderSize(long size) {
  
  	long leftSide = size / 1024;
  	long rightSide = (size % 1024) / 103;	// Makes 1 digit
  	if ((leftSide == 0) && (rightSide == 0) && (size > 0))
  	    rightSide = 1;
  
  	return ("" + leftSide + "." + rightSide + " kb");
  
      }
  
  
      /**
       * URL rewriter.
       * 
       * @param path Path which has to be rewiten
       */
      private String rewriteUrl(String path) {
          
          String normalized = path;
          
  	// Replace " " with "%20"
  	while (true) {
  	    int index = normalized.indexOf(" ");
  	    if (index < 0)
  		break;
  	    normalized = normalized.substring(0, index) + "%20"
  		+ normalized.substring(index + 1);
  	}
  
          return normalized;
          
      }
  
  
  }
  
  
  
  1.1                  jakarta-tomcat/src/webdav/org/apache/tomcat/webdav/resources/FileResources.java
  
  Index: FileResources.java
  ===================================================================
  /*
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */
  
  package org.apache.tomcat.webdav.resources;
  
  
  import java.io.ByteArrayInputStream;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.InputStream;
  import java.io.FileOutputStream;
  import java.io.FileNotFoundException;
  import java.io.OutputStream;
  import java.io.IOException;
  import java.net.MalformedURLException;
  import java.net.URL;
  import java.util.ArrayList;
  import java.util.List;
  import org.apache.tomcat.webdav.util.StringManager;
  
  /**
   * Implementation of the <b>Resources</b> that operates off a document
   * base that is a directory in the local filesystem.  If the specified
   * document base is relative, it is resolved against the application base
   * directory for our surrounding virtual host (if any), or against the
   * value of the "catalina.home" system property.
   * <p>
   * <strong>IMPLEMENTATION NOTE</strong>:  It is assumed that new files may
   * be added, and existing files modified, while this web application is
   * running.  Therefore, only resources (not directories) are cached, and
   * the background thread must remove cached entries that have been modified
   * since they were cached.
   *
   * @author Craig R. McClanahan
   * @author Remy Maucherat
   * @version $Revision: 1.1 $ $Date: 2000/11/03 21:27:41 $
   */
  public final class FileResources extends ResourcesBase {
  
  
      // ----------------------------------------------------- Instance Variables
  
      // title used when rendering directory resources ( no other use )
      String contextPath;
  
      public static String serverInfo="Apache Tomcat/3.3";
      // XXX add code to set it
      
  
      /**
       * The document base directory for this component.
       */
      protected File base = null;
  
  
      /**
       * The descriptive information string for this implementation.
       */
      protected static final String info =
  	"org.apache.catalina.resources.FileResources/1.0";
  
  
      /**
       * The descriptive information string for this implementation.
       */
      protected static final int BUFFER_SIZE = 2048;
  
  
      // ------------------------------------------------------------- Properties
  
  
      /**
       * Set the document root for this component.
       *
       * @param docBase The new document root - it must be an absolute path
       *
       * @exception IllegalArgumentException if the specified value is not
       *  supported by this implementation
       * @exception IllegalArgumentException if this would create a
       *  malformed URL
       */
      public void setDocBase(String docBase) {
  
  	// Validate the format of the proposed document root
  	if (docBase == null)
  	    throw new IllegalArgumentException
  		(sm.getString("resources.null"));
  
  	// Calculate a File object referencing this document base directory
  	File base = new File(docBase);
  
  	// Validate that the document base is an existing directory
  	if (!base.exists() || !base.isDirectory() || !base.canRead())
  	    throw new IllegalArgumentException
  		(sm.getString("fileResources.base", docBase));
  	this.base = base;
  
  	// Perform the standard superclass processing
  	super.setDocBase(docBase);
  
      }
  
      public void setContextPath( String cp ) {
  	contextPath=cp;
      }
  
      // --------------------------------------------------------- Public Methods
  
  
      /**
       * Return the real path for a given virtual path, or <code>null</code>
       * if no such path can be identified.
       *
       * @param path Context-relative path starting with '/'
       *
       * @exception IllegalArgumentException if the path argument is null
       *  or does not start with a '/'
       */
      public String getRealPath(String path) {
  
          String normalized = ResourceUtils.normalize(path);
  	if (normalized == null) {
              //            if (debug >= 1)
              //                log("getRealPath(" + path + ") --> NULL");
  	    return (null);
          }
          try {
              ResourceUtils.validate(normalized);
          } catch (IllegalArgumentException e) {
              //            if (debug >= 1)
              //                log("getRealPath(" + path + ") --> IAE");
              throw e;
          }
  
  	// Return a real path to where this file does, or would, exist
  	File file = new File(base, normalized.substring(1));
          //        if (debug >= 1)
          //            log("getRealPath(" + path + ") --> " + file.getAbsolutePath());
  	return (file.getAbsolutePath());
  
      }
  
  
      /**
       * Return a URL to the resource specified by the given virtual path,
       * or <code>null</code> if no such URL can be identified.
       * <p>
       * <b>IMPLEMENTATION NOTE</b>:  Use of this method bypasses any caching
       * performed by this component.  To take advantage of local caching,
       * use <code>getResourceAsStream()</code> instead.
       *
       * @param path Context-relative path starting with '/'
       *
       * @exception IllegalArgumentException if the path argument is null
       *  or does not start with a '/'
       * @exception MalformedURLException if the resulting URL does not
       *  have legal syntax
       */
      public URL getResource(String path) throws MalformedURLException {
  
  	// Acquire an absolute pathname for the requested resource
  	String pathname = getRealPath(path);
  	if (pathname == null) {
              //            if (debug >= 1)
              //                log("getResource(" + path + ") --> NULL");
  	    return (null);
          }
  
  	// Construct a URL that refers to this file
          URL url = new URL("file", null, 0, pathname);
          //        if (debug >= 1)
          //            log("getResource(" + path + ") --> " + url.toString());
          return (url);
  
      }
  
  
      /**
       * Return an InputStream to the contents of the resource specified
       * by the given virtual path, or <code>null</code> if no resource
       * exists at the specified path.
       *
       * @param path Context-relative path starting with '/'
       *
       * @exception IllegalArgumentException if the path argument is null
       *  or does not start with a '/'
       */
      public InputStream getResourceAsStream(String path) {
  
  	String normalized = ResourceUtils.normalize(path);
  	if (normalized == null) {
              //            if (debug >= 1)
              //                log("getResourceAsStream(" + path + ") --> NULL");
  	    return (null);
          }
          try {
              ResourceUtils.validate(normalized);
          } catch (IllegalArgumentException e) {
              //            if (debug >= 1)
              //                log("getResourceAsStream(" + path + ") --> IAE");
              throw e;
          }
  
  	// Look up the cached resource entry (if it exists) for this path
  	ResourceBean resource = null;
  	synchronized (resourcesCache) {
  	    resource = (ResourceBean) resourcesCache.get(normalized);
  	}
  	if (resource != null) {
              //            if (debug >= 1)
              //                log("getResourceAsStream(" + path + ") --> CACHED");
  	    return (new ByteArrayInputStream(resource.getData()));
          }
  
  	// Create a File object referencing the requested resource
  	File file = file(normalized);
  	if ((file == null) || !file.exists() || !file.canRead()) {
              //            if (debug >= 1)
              //                log("getResourceAsStream(" + path + ") --> NO FILE");
  	    return (null);
          }
  
          // If the resource path ends in "/", this *must* be a directory
          if (normalized.endsWith("/") && !file.isDirectory()) {
              //            if (debug >= 1)
              //                log("getResourceAsStream(" + path + ") --> NOT DIR");
              return (null);
          }
  
          // Special handling for directories
  	if (file.isDirectory()) {
  	    if (contextPath == null)
  		contextPath = "";
  	    DirectoryBean directory = new DirectoryBean(normalized, file);
              File[] fileList = file.listFiles();
              for (int i=0; i<fileList.length; i++) {
                  File currentFile = fileList[i];
                  ResourceBean newEntry = null;
                  if (currentFile.isDirectory()) {
                      newEntry =
                          new DirectoryBean(ResourceUtils.normalize(normalized + "/"
                                                      + currentFile.getName()),
                                                      currentFile);
                  } else {
                      newEntry =
                          new ResourceBean(ResourceUtils.normalize(normalized + "/"
                                                     + currentFile.getName()),
                                                     currentFile);
                  }
                  directory.addResource(newEntry);
              }
              //            if (debug >= 1)
              //                log("getResourceAsStream(" + path + ") --> DIRECTORY");
  	    return (directory.render(contextPath, serverInfo));
  	}
  
  	// Cache the data for this resource (if appropriate and not yet done)
  	if (cacheable(normalized, file.length())) {
  	    resource = new ResourceBean(normalized, file);
  	    try {
  		resource.cache(inputStream(resource.getName()));
  	    } catch (IOException e) {
  		log(sm.getString("resources.input", resource.getName()), e);
  		return (null);
  	    }
  	    synchronized (resourcesCache) {
  		resourcesCache.put(resource.getName(), resource);
  		resourcesCount++;
  	    }
              //            if (debug >= 1)
              //                log("getResourceAsStream(" + path + ") --> CACHE AND SERVE");
  	    return (new ByteArrayInputStream(resource.getData()));
  	}
  
  	// Serve the contents directly from the filesystem
  	try {
              //            if (debug >= 1)
              //                log("getResourceAsStream(" + path + ") --> SERVE FILE");
  	    return (new FileInputStream(file));
  	} catch (IOException e) {
  	    log(sm.getString("resoruces.input", resource.getName()), e);
  	    return (null);
  	}
  
      }
  
  
      /**
       * Returns true if a resource exists at the specified path,
       * where <code>path</code> would be suitable for passing as an argument to
       * <code>getResource()</code> or <code>getResourceAsStream()</code>.
       * If there is no resource at the specified location, return false.
       *
       * @param path The path to the desired resource
       */
      public boolean exists(String path) {
  
          String normalized = ResourceUtils.normalize(path);
  	if (normalized == null) {
              //            if (debug >= 1)
              //                log("exists(" + path + ") --> NULL");
  	    return (false);
          }
          try {
              ResourceUtils.validate(normalized);
          } catch (IllegalArgumentException e) {
              //            if (debug >= 1)
              //                log("exists(" + path + ") --> IAE");
              throw e;
          }
  
  	File file = new File(base, normalized.substring(1));
          if (file != null) {
              //            if (debug >= 1)
              //                log("exists(" + path + ") --> " + file.exists() +
              //                    " isDirectory=" + file.isDirectory());
              return (file.exists());
          } else {
              //            if (debug >= 1)
              //                log("exists(" + path + ") --> NO FILE");
              return (false);
          }
  
      }
  
  
      /**
       * Return the last modified time for the resource specified by
       * the given virtual path, or -1 if no such resource exists (or
       * the last modified time cannot be determined).
       * <p>
       * <strong>IMPLEMENTATION NOTE</strong>: We are assuming that
       * files may be modified while the application is running, so we
       * bypass the cache and check the filesystem directly.
       *
       * @param path Context-relative path starting with '/'
       *
       * @exception IllegalArgumentException if the path argument is null
       *  or does not start with a '/'
       */
      public long getResourceModified(String path) {
  
          String normalized = ResourceUtils.normalize(path);
  	if (normalized == null)
  	    return (-1L);
  	ResourceUtils.validate(normalized);
  
  	File file = file(normalized);
          if (file != null)
  	    return (file.lastModified());
  	else
  	    return (-1L);
  
      }
  
  
      /**
       * Return the set of context-relative paths of all available resources.
       * Each path will begin with a "/" character.
       */
       public String[] getResourcePaths() {
  
          ArrayList paths = new ArrayList();
          paths.add("/"); // NOTE: Assumes directories are included
          appendResourcePaths(paths, "", base);
          String results[] = new String[paths.size()];
          return (results);
  
       }
  
  
      /**
       * Return the creation date/time of the resource at the specified
       * path, where <code>path</code> would be suitable for passing as an
       * argument to <code>getResource()</code> or
       * <code>getResourceAsStream()</code>.  If there is no resource at the
       * specified location, return -1. If this time is unknown, the
       * implementation should return getResourceModified(path).
       * <p>
       * <strong>IMPLEMENTATION NOTE</strong>: The creation date of a file
       * shouldn't change except if the file is deleted and the recreated, so
       * this method uses the cache.
       *
       * @param path Context-relative path starting with '/'
       *
       * @exception IllegalArgumentException if the path argument is null
       *  or does not start with a '/'
       */
      public long getResourceCreated(String path) {
  
          String normalized = ResourceUtils.normalize(path);
  	if (normalized == null)
  	    return (-1L);
  	ResourceUtils.validate(normalized);
  
  	File file = file(normalized);
  	if (file != null)
  	    return (file.lastModified());
  	else
  	    return (-1L);
  
      }
  
  
      /**
       * Return the content length of the resource at the specified
       * path, where <code>path</code> would be suitable for passing as an
       * argument to <code>getResource()</code> or
       * <code>getResourceAsStream()</code>.  If the content length
       * of the resource can't be determined, return -1. If no content is
       * available (when for exemple, the resource is a collection), return 0.
       *
       * @param path The path to the desired resource
       */
      public long getResourceLength(String path) {
  
          String normalized = ResourceUtils.normalize(path);
  	if (normalized == null)
  	    return (-1L);
  	ResourceUtils.validate(normalized);
  
  	// Look up the cached resource entry (if it exists) for this path
  	ResourceBean resource = null;
  	synchronized (resourcesCache) {
  	    resource = (ResourceBean) resourcesCache.get(normalized);
  	}
          if (resource != null) {
              return (resource.getSize());
          }
  
          // No entry was found in the cache
  	File file = file(normalized);
  	if (file != null)
  	    return (file.length());
  	else
  	    return (-1L);
  
      }
  
  
      /**
       * Return true if the resource at the specified path is a collection. A
       * collection is a special type of resource which has no content but
       * contains child resources.
       *
       * @param path The path to the desired resource
       */
      public boolean isCollection(String path) {
  
          String normalized = ResourceUtils.normalize(path);
  	if (normalized == null)
  	    return (false);
  	ResourceUtils.validate(normalized);
  
  	File file = file(normalized);
  	if (file != null)
  	    return (file.isDirectory());
  	else
  	    return (false);
  
      }
  
  
      /**
       * Return the children of the resource at the specified path, if any. This
       * will return null if the resource is not a collection, or if it is a
       * collection but has no children.
       *
       * @param path The path to the desired resource
       */
      public String[] getCollectionMembers(String path) {
  
          String normalized = ResourceUtils.normalize(path);
  	if (normalized == null)
  	    return (null);
  	ResourceUtils.validate(normalized);
  
  	File file = file(normalized);
  	if (file != null) {
  	    String[] dirList = file.list();
              for (int i=0; i<dirList.length; i++) {
                  dirList[i] = ResourceUtils.normalize(normalized + "/" + dirList[i]);
              }
              return dirList;
          } else {
  	    return (null);
          }
  
      }
  
  
      /**
       * Set the content of the resource at the specified path. If the resource
       * already exists, its previous content is overwritten. If the resource
       * doesn't exist, its immediate parent collection (according to the path
       * given) exists, then its created, and the given content is associated
       * with it. Return false if either the resource is a collection, or
       * no parent collection exist.
       *
       * @param path The path to the desired resource
       * @param content InputStream to the content to be set
       */
      public boolean setResource(String path, InputStream content) {
  
          String normalized = ResourceUtils.normalize(path);
  	if (normalized == null)
  	    return (false);
  	ResourceUtils.validate(normalized);
  
  	File file = new File(base, normalized.substring(1));
          //if ((file.exists()) && (file.isDirectory()))
          //return (false);
  
          OutputStream os = null;
  
          try {
              os = new FileOutputStream(file);
          } catch (FileNotFoundException e) {
              return (false);
  	} catch (IOException e) {
  	  return (false);
          }
  
          try {
              byte[] buffer = new byte[BUFFER_SIZE];
              while (true) {
                  int nb = content.read(buffer);
                  if (nb == -1)
                      break;
                  os.write(buffer, 0, nb);
              }
          } catch (IOException e) {
              return (false);
          }
  
          try {
              os.close();
          } catch (IOException e) {
              return (false);
          }
  
          try {
              content.close();
          } catch (IOException e) {
              return (false);
          }
  
          return (true);
  
      }
  
  
      /**
       * Create a collection at the specified path. A parent collection for this
       * collection must exist. Return false if a resource already exist at the
       * path specified, or if the parent collection doesn't exist.
       *
       * @param path The path to the desired resource
       */
      public boolean createCollection(String path) {
  
          String normalized = ResourceUtils.normalize(path);
  	if (normalized == null)
  	    return (false);
  	ResourceUtils.validate(normalized);
  
  	File file = new File(base, normalized.substring(1));
  	if (file != null)
  	    return (file.mkdir());
  	else
  	    return (false);
  
      }
  
  
      /**
       * Delete the specified resource. Non-empty collections cannot be deleted
       * before deleting all their member resources. Return false is deletion
       * fails because either the resource specified doesn't exist, or the
       * resource is a non-empty collection.
       *
       * @param path The path to the desired resource
       */
      public boolean deleteResource(String path) {
  
          String normalized = ResourceUtils.normalize(path);
  	if (normalized == null) {
              return (false);
          }
  	ResourceUtils.validate(normalized);
  
  	File file = file(normalized);
  	if (file != null) {
              return (file.delete());
  	} else {
  	    return (false);
          }
  
      }
  
  
      // -------------------------------------------------------- Private Methods
  
  
      /**
       * Append resource paths for files in the specified directory to the
       * list we are accumulating.
       *
       * @param paths The list containing our accumulated paths
       * @param path Context-relative path for this directory
       * @param dir File object for this directory
       */
      private void appendResourcePaths(List paths, String path, File dir) {
  
          String names[] = dir.list();
          for (int i = 0; i < names.length; i++) {
              paths.add(path + "/" + names[i]);
              File file = new File(dir, names[i]);        // Assume dirs included
              if (file.isDirectory())
                  appendResourcePaths(paths, path + "/" + names[i], file);
          }
  
      }
  
  
      /**
       * Return a File object representing the specified normalized
       * context-relative path if it exists and is readable.  Otherwise,
       * return <code>null</code>.
       *
       * @param name Normalized context-relative path (with leading '/')
       */
      private File file(String name) {
  
  	if (name == null)
  	    return (null);
  	File file = new File(base, name.substring(1));
  	if (file.exists() && file.canRead())
  	    return (file);
  	else
  	    return (null);
  
      }
  
  
      /**
       * Return an input stream to the data content of the underlying file
       * that corresponds to the specified normalized context-relative path.
       *
       * @param name Normalized context-relative path (with leading '/')
       *
       * @exception IOException if an input/output error occurs
       */
      private InputStream inputStream(String name) throws IOException {
  
  	File file = file(name);
  	if ((file == null) || file.isDirectory())
  	    return (null);
  	else
  	    return (new FileInputStream(file));
  
      }
  
  
  }
  
  
  
  1.1                  jakarta-tomcat/src/webdav/org/apache/tomcat/webdav/resources/JarResources.java
  
  Index: JarResources.java
  ===================================================================
  /*
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */
  
  
  package org.apache.tomcat.webdav.resources;
  
  
  import java.io.ByteArrayInputStream;
  import java.io.InputStream;
  import java.io.IOException;
  import java.net.JarURLConnection;
  import java.net.MalformedURLException;
  import java.net.URL;
  import java.util.ArrayList;
  import java.util.Enumeration;
  import java.util.Iterator;
  import java.util.jar.JarEntry;
  import java.util.jar.JarFile;
  import org.apache.tomcat.webdav.util.StringManager;
  
  /**
   * Implementation of the <b>Resources</b> that decompresses and renders
   * entries from a JAR file that is located either locally or remotely.
   * Valid syntax for the <code>docBase</code> property corresponds to the
   * syntax supported by the <code>java.net.JarURLConnection</code> class,
   * and is illustrated by the following examples:
   * <ul>
   * <li><b>jar:file:/path/to/filename.jar!/</b> - Uses the JAR file
   *     <code>/path/to/filename.jar</code> as the source of resources.
   * <li><b>jar:http://www.foo.com/bar/baz.jar!/</b> - Uses the JAR file
   *     retrieved by doing an HTTP GET operation on the URL
   *     <code>http://www.foo.com/bar/baz.jar</code> (which makes this
   *     instance of Catalina serve as a proxy for the specified application).
   * </ul>
   * In all cases, the <code>docBase</code> you specify must begin with
   * <code>jar:</code>.  If your <code>docBase</code> value does not end with
   * "!/", this will be added for you.
   * <p>
   * <strong>IMPLEMENTATION NOTE</strong>:  It is assumed that the underlying
   * JAR file itself will not be modified without restarting this web application
   * (or at least this <code>Resources</code> implementation).  Therefore, the
   * set of directory and resource entries is pre-loaded into our resource cache.
   * The actual data associated with these resources is not cached until it is
   * requested the first time (and passes the "cacheable" test).
   *
   * @author Craig R. McClanahan
   * @version $Revision: 1.1 $ $Date: 2000/11/03 21:27:41 $
   */
  
  public final class JarResources extends ResourcesBase {
  
  
      // ----------------------------------------------------- Instance Variables
  
      // title used when rendering directory resources ( no other use )
      String contextPath;
      public static String serverInfo="Apache Tomcat/3.3";
      // XXX add code to set it
  
      /**
       * The URLConnection to our JAR file.
       */
      protected JarURLConnection conn = null;
  
  
      /**
       * The descriptive information string for this implementation.
       */
      protected static final String info =
  	"org.apache.catalina.resources.JarResources/1.0";
  
  
      /**
       * The JarFile object associated with our document base.
       */
      protected JarFile jarFile = null;
  
  
      // ------------------------------------------------------------- Properties
  
  
      /**
       * Set the document root for this component.
       *
       * @param docBase The new document root
       *
       * @exception IllegalArgumentException if the specified value is not
       *  supported by this implementation
       * @exception IllegalArgumentException if this would create a
       *  malformed URL
       */
      public void setDocBase(String docBase) {
  
  	// Validate the format of the proposed document root
  	if (docBase == null)
  	    throw new IllegalArgumentException
  		(sm.getString("resources.null"));
  	if (!docBase.startsWith("jar:"))
  	    throw new IllegalArgumentException
  		(sm.getString("jarResources.syntax", docBase));
  	if (!docBase.endsWith("!/"))
  	    docBase += "!/";
  
  	// Close any previous JAR that we have opened
  	if (jarFile != null) {
  	    try {
  		jarFile.close();
  	    } catch (IOException e) {
  		log("Closing JAR file", e);
  	    }
  	    jarFile = null;
  	    conn = null;
  	}
  
  	// Open a URLConnection to the specified JAR file
  	try {
  	    URL url = new URL(docBase);
  	    conn = (JarURLConnection) url.openConnection();
  	    conn.setAllowUserInteraction(false);
  	    conn.setDoInput(true);
  	    conn.setDoOutput(false);
  	    conn.connect();
  	    jarFile = conn.getJarFile();
  	} catch (Exception e) {
  	    log("Establishing connection", e);
  	    throw new IllegalArgumentException
  		(sm.getString("resources.connect", docBase));
  	}
  
  	// Populate our cache of directory and resource entries
  	populate();
  
  	// Perform the standard superclass processing
  	super.setDocBase(docBase);
  
      }
  
      public void setContextPath( String cp ) {
  	contextPath=cp;
      }
  
      // --------------------------------------------------------- Public Methods
  
  
      /**
       * Return the real path for a given virtual path, or <code>null</code>
       * if no such path can be identified.
       *
       * @param path Context-relative path starting with '/'
       *
       * @exception IllegalArgumentException if the path argument is null
       *  or does not start with a '/'
       */
      public String getRealPath(String path) {
  
  	ResourceUtils.validate(path);
  	return (null);	// JAR entries do not have a real path
  
      }
  
  
      /**
       * Return a URL to the resource specified by the given virtual path,
       * or <code>null</code> if no such URL can be identified.
       * <p>
       * <b>IMPLEMENTATION NOTE</b>:  Use of this method bypasses any caching
       * performed by this component.  To take advantage of local caching,
       * use <code>getResourceAsStream()</code> instead.
       *
       * @param path Context-relative path starting with '/'
       *
       * @exception IllegalArgumentException if the path argument is null
       *  or does not start with a '/'
       * @exception MalformedURLException if the resulting URL does not
       *  have legal syntax
       */
      public URL getResource(String path) throws MalformedURLException {
  
  	ResourceUtils.validate(path);
  
  	// Construct a URL from the normalized version of the specified path
  	String normalized = ResourceUtils.normalize(path);
  	if (normalized != null)
  	    return (new URL(docBase + normalized.substring(1)));
  	else
  	    return (null);
  
      }
  
  
      /**
       * Return an InputStream to the contents of the resource specified
       * by the given virtual path, or <code>null</code> if no resource
       * exists at the specified path.
       *
       * @param path Context-relative path starting with '/'
       *
       * @exception IllegalArgumentException if the path argument is null
       *  or does not start with a '/'
       */
      public InputStream getResourceAsStream(String path) {
  
  	ResourceUtils.validate(path);
  
  	// Look up the cached resource entry (if it exists) for this path
  	String normalized = ResourceUtils.normalize(path);
  	if (normalized == null)
  	    return (null);
  	ResourceBean resource = null;
  	synchronized (resourcesCache) {
  	    resource = (ResourceBean) resourcesCache.get(normalized);
  	}
  	if (resource == null)
  	    return (null);
  
  	// Special handling for directories
  	if (resource instanceof DirectoryBean) {
  	    if (expand) {
  		if (contextPath == null)
  		    contextPath = "";
  		return (((DirectoryBean) resource).render(contextPath,
  							  serverInfo));
  	    } else {
  		return (null);
  	    }
  	}
  
  	// Cache the data for this resource (if appropriate and not yet done)
  	if (resource.getData() == null) {
  	    try {
  		resource.cache(inputStream(resource.getName()));
  	    } catch (IOException e) {
  		log(sm.getString("resources.input", resource.getName()), e);
  		return (null);
  	    }
  	    if (resource.getData() != null)
  		resourcesCount++;
  	}
  
  	// Return an input stream to the cached or uncached data
  	if (resource.getData() != null)
  	    return (new ByteArrayInputStream(resource.getData()));
  	else {
  	    try {
  		return (inputStream(normalized));
  	    } catch (IOException e) {
  		log(sm.getString("resources.input", resource.getName()), e);
  		return (null);
  	    }
  	}
  
      }
  
  
      /**
       * Returns true if a resource exists at the specified path,
       * where <code>path</code> would be suitable for passing as an argument to
       * <code>getResource()</code> or <code>getResourceAsStream()</code>.
       * If there is no resource at the specified location, return false.
       *
       * @param path The path to the desired resource
       */
      public boolean exists(String path) {
  
  	// Look up and return the last modified time for this resource
  	String normalized = ResourceUtils.normalize(path);
  	if (normalized == null)
  	    return (false);
  	ResourceUtils.validate(normalized);
  
  	ResourceBean resource = null;
  	synchronized (resourcesCache) {
  	    resource = (ResourceBean) resourcesCache.get(normalized);
  	}
  	if (resource != null)
  	    return (true);
  	else
  	    return (false);
  
      }
  
  
      /**
       * Return the last modified time for the resource specified by
       * the given virtual path, or -1 if no such resource exists (or
       * the last modified time cannot be determined).
       * <p>
       * <strong>IMPLEMENTATION NOTE</strong>: We are assuming that the
       * underlying JAR file will not be modified without restarting our
       * associated Context, so it is sufficient to return the last modified
       * timestamp from our cached resource bean.
       *
       * @param path Context-relative path starting with '/'
       *
       * @exception IllegalArgumentException if the path argument is null
       *  or does not start with a '/'
       */
      public long getResourceModified(String path) {
  
  	ResourceUtils.validate(path);
  
  	// Look up and return the last modified time for this resource
  	String normalized = ResourceUtils.normalize(path);
  	if (normalized == null)
  	    return (-1L);
  	ResourceBean resource = null;
  	synchronized (resourcesCache) {
  	    resource = (ResourceBean) resourcesCache.get(normalized);
  	}
  	if (resource != null)
  	    return (resource.getLastModified());
  	else
  	    return (-1L);
  
      }
  
  
      /**
       * Return the set of context-relative paths of all available resources.
       * Each path will begin with a "/" character.
       */
       public String[] getResourcePaths() {
  
          ArrayList paths = new ArrayList();
          // NOTE: assumes directories are included
          synchronized (resourcesCache) {
              Iterator names = resourcesCache.keySet().iterator();
              while (names.hasNext())
                  paths.add((String) names.next());
          }
          String results[] = new String[paths.size()];
          return ((String[]) paths.toArray(results));
  
       }
  
  
      /**
       * Return the creation date/time of the resource at the specified
       * path, where <code>path</code> would be suitable for passing as an
       * argument to <code>getResource()</code> or
       * <code>getResourceAsStream()</code>.  If there is no resource at the
       * specified location, return -1. If this time is unknown, the
       * implementation should return getResourceModified(path).
       *
       * @param path The path to the desired resource
       */
      public long getResourceCreated(String path) {
          return 0;
      }
  
  
      /**
       * Return the content length of the resource at the specified
       * path, where <code>path</code> would be suitable for passing as an
       * argument to <code>getResource()</code> or
       * <code>getResourceAsStream()</code>.  If the content length
       * of the resource can't be determined, return -1. If no content is
       * available (when for exemple, the resource is a collection), return 0.
       *
       * @param path The path to the desired resource
       */
      public long getResourceLength(String path) {
          return -1;
      }
  
  
      /**
       * Return true if the resource at the specified path is a collection. A
       * collection is a special type of resource which has no content but
       * contains child resources.
       *
       * @param path The path to the desired resource
       */
      public boolean isCollection(String path) {
          return false;
      }
  
  
      /**
       * Return the children of the resource at the specified path, if any. This
       * will return null if the resource is not a collection, or if it is a
       * collection but has no children.
       *
       * @param path The path to the desired resource
       */
      public String[] getCollectionMembers(String path) {
          return null;
      }
  
  
      /**
       * Set the content of the resource at the specified path. If the resource
       * already exists, its previous content is overwritten. If the resource
       * doesn't exist, its immediate parent collection (according to the path
       * given) exists, then its created, and the given content is associated
       * with it. Return false if either the resource is a collection, or
       * no parent collection exist.
       *
       * @param path The path to the desired resource
       * @param content InputStream to the content to be set
       */
      public boolean setResource(String path, InputStream content) {
          return false;
      }
  
  
      /**
       * Create a collection at the specified path. A parent collection for this
       * collection must exist. Return false if a resource already exist at the
       * path specified, or if the parent collection doesn't exist.
       *
       * @param path The path to the desired resource
       */
      public boolean createCollection(String path) {
          return false;
      }
  
  
      /**
       * Delete the specified resource. Non-empty collections cannot be deleted
       * before deleting all their member resources. Return false is deletion
       * fails because either the resource specified doesn't exist, or the
       * resource is a non-empty collection.
       *
       * @param path The path to the desired resource
       */
      public boolean deleteResource(String path) {
          return false;
      }
  
  
      // -------------------------------------------------------- Private Methods
  
  
      /**
       * Return an input stream to the data content of the underlying JAR entry
       * that corresponds to the specified normalized context-relative path.
       *
       * @param name Normalized context-relative path (with leading '/')
       *
       * @exception IOException if an input/output error occurs
       */
      private InputStream inputStream(String name) throws IOException {
  
  	if (name == null)
  	    return (null);
  	JarEntry entry = jarFile.getJarEntry(name.substring(1));
  	if (entry == null)
  	    return (null);
  	return (jarFile.getInputStream(entry));
  
      }
  
  
      /**
       * Populate our resources cache based on all of the entries in the
       * underlying JAR file.
       * <p>
       * <strong>IMPLEMENTATION NOTE</strong>: This method assumes that the
       * "name" of an entry within the JAR file is exactly the same as the
       * result of performing a <code>normalize()</code> call on that name,
       * with the exception of the leading slash that is added.
       */
      private void populate() {
  
  	synchronized (resourcesCache) {
  
  	    // Erase the existing cache
  	    resourcesCache.clear();
  	    resourcesCount = 0;
  
  	    // Construct a pseudo-directory for the entire JAR file
  	    DirectoryBean top = new DirectoryBean("/");
  	    resourcesCache.put(top.getName(), top);
  
  	    // Process the entries in this JAR file
  	    Enumeration entries = jarFile.entries();
  	    while (entries.hasMoreElements()) {
  
  		JarEntry entry = (JarEntry) entries.nextElement();
  
  		// Create and cache the resource for this entry
  		String name = "/" + entry.getName();
  		ResourceBean resource = null;
  		if (entry.isDirectory())
  		    resource = new DirectoryBean(name, entry);
  		else
  		    resource = new ResourceBean(name, entry);
  
  		// Connect to our parent entry (if any)
  		int last = name.lastIndexOf("/");
  		String parentName = name.substring(0, last);
  		if (parentName.length() < 1)
  		    parentName = "/";
  		ResourceBean parent =
  		    (ResourceBean) resourcesCache.get(parentName);
  		if ((parent != null) && (parent instanceof DirectoryBean))
  		    resource.setParent((DirectoryBean) parent);
  
  	    }
  
  	}
  
      }
  
  
      // ------------------------------------------------------ Lifecycle Methods
  
  
      /**
       * Shut down this component.
       *
       * @exception LifecycleException if a major problem occurs
       */
      public void stop()  {
  
  	// Shut down our current connection (if any)
  	if (jarFile != null) {
  	    try {
  		jarFile.close();
  	    } catch (IOException e) {
  		log("Closing JAR file", e);
  	    }
  	    jarFile = null;
  	}
  	conn = null;
  
  	// Perform standard superclass processing
  	super.stop();
  
      }
  
  
  }
  
  
  
  1.1                  jakarta-tomcat/src/webdav/org/apache/tomcat/webdav/resources/LocalStrings.properties
  
  Index: LocalStrings.properties
  ===================================================================
  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
  fileResources.base=Document base {0} does not exist or is not a readable directory
  jarResources.syntax=Document base {0} must start with 'jar:' and end with '!/'
  resources.alreadyStarted=Resources has already been started
  resources.connect=Cannot connect to document base {0}
  resources.input=Cannot create input stream for resource {0}
  resources.notStarted=Resources has not yet been started
  resources.null=Document base cannot be null
  resources.path=Context relative path {0} must start with '/'
  standardResources.alreadyStarted=Resources has already been started
  standardResources.directory=File base {0} is not a directory
  standardResources.exists=File base {0} does not exist
  standardResources.notStarted=Resources has not yet been started
  standardResources.null=Document base cannot be null
  standardResources.slash=Document base {0} must not end with a slash
  
  
  
  1.1                  jakarta-tomcat/src/webdav/org/apache/tomcat/webdav/resources/ResourceBean.java
  
  Index: ResourceBean.java
  ===================================================================
  /*
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999 The Apache Software Foundation.  All rights 
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer. 
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:  
   *       "This product includes software developed by the 
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written 
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */ 
  
  
  package org.apache.tomcat.webdav.resources;
  
  
  import java.io.BufferedInputStream;
  import java.io.ByteArrayOutputStream;
  import java.io.File;
  import java.io.InputStream;
  import java.io.IOException;
  import java.util.jar.JarEntry;
  
  
  /**
   * Abstraction bean that represents the properties of a "resource" that
   * may or may not be a "directory", in a fashion that is independent
   * of the actual underlying medium used to represent those entries.
   * Convenient constructors are provided to populate our properties from
   * common sources, but it is feasible to do everything with property
   * setters if necessary.
   *
   * @author Craig R. McClanahan
   * @version $Revision: 1.1 $ $Date: 2000/11/03 21:27:42 $
   */
  
  public class ResourceBean {
  
  
      // ----------------------------------------------------------- Constructors
  
  
      /**
       * Construct a new resource bean for the named resource, with default
       * properties.
       *
       * @param name Normalized context-relative name of this resource
       */
      public ResourceBean(String name) {
  
  	super();
  	setName(name);
  
      }
  
  
      /**
       * Construct a new resource bean for the named resource, with properties
       * populated from the specified object.  Note that the data content of
       * this resource is <strong>not</strong> initialized unless and until
       * <code>setData()</code> is called.
       *
       * @param name Normalized context-relative name of this resource
       * @param file File representing this resource entry
       */
      public ResourceBean(String name, File file) {
  
  	this(name);
  	populate(file);
  
      }
  
  
      /**
       * Construct a new resource bean for the named resource, with properties
       * populated from the specified object.  Note that the data content of
       * this resource is <strong>not</strong> initialized unless and until
       * <code>setData()</code> is called.
       *
       * @param name Normalized context-relative name of this resource
       * @param entry JAR entry representing this resource entry
       */
      public ResourceBean(String name, JarEntry entry) {
  
  	this(name);
  	populate(entry);
  
      }
  
  
      // ------------------------------------------------------------- Properties
  
  
      /**
       * The data content of this resource.  This property is
       * <strong>only</strong> initialized when the corresponding property
       * setter method is called.
       */
      protected byte[] data = null;
  
      public byte[] getData() {
  	return (this.data);
      }
  
      public void setData(byte[] data) {
  	this.data = data;
      }
  
  
      /**
       * The last modified date/time for this resource, in milliseconds since
       * the epoch.
       */
      protected long lastModified = 0L;
  
      public long getLastModified() {
  	return (this.lastModified);
      }
  
      public void setLastModified(long lastModified) {
  	this.lastModified = lastModified;
      }
  
  
      /**
       * The normalized context-relative name of this resource.
       */
      protected String name = null;
  
      public String getName() {
  	return (this.name);
      }
  
      public void setName(String name) {
  	this.name = name;
      }
  
  
  
      /**
       * The parent resource (normally a directory entry) of this resource.
       * Note that this property is <strong>not</strong> set from an underlying
       * File or JarEntry argument to our constructor -- you must call
       * <code>setParent()</code> explicitly if you wish to maintain this
       * relationship.
       */
      protected DirectoryBean parent = null;
  
      public DirectoryBean getParent() {
  	return (this.parent);
      }
  
      public void setParent(DirectoryBean parent) {
  	if (this.parent != null)
  	    this.parent.removeResource(this);
  	this.parent = parent;
  	if (this.parent != null)
  	    this.parent.addResource(this);
      }
  
  
      /**
       * The size of this resource, in bytes.
       */
      protected long size = 0L;
  
      public long getSize() {
  	return (this.size);
      }
  
      public void setSize(long size) {
  	this.size = size;
      }
  
  
      // --------------------------------------------------------- Public Methods
  
  
      /**
       * Cache the data for this resource from the specified input stream.
       *
       * @param input InputStream from which to read the data for this resource
       *
       * @exception IOException if an input/output error occurs
       */
      public void cache(InputStream input) throws IOException {
  
  	BufferedInputStream in = new BufferedInputStream(input);
  	ByteArrayOutputStream out = new ByteArrayOutputStream();
  	while (true) {
  	    int ch = in.read();
  	    if (ch < 0)
  		break;
  	    out.write(ch);
  	}
  	in.close();
  	data = out.toByteArray();
  
      }
  
  
      // ------------------------------------------------------ Protected Methods
  
  
      /**
       * Populate our properties from the specified File object.
       *
       * @param file File representing this entry
       */
      protected void populate(File file) {
  
  	this.lastModified = file.lastModified();
  	this.size = file.length();
  
      }
  
  
      /**
       * Populate our properties from the specified JarEntry object.
       *
       * @param entry JarEntry representing this entry
       */
      protected void populate(JarEntry entry) {
  
  	this.lastModified = entry.getTime();
  	this.size = entry.getSize();
  
      }
  
  
  }
  
  
  
  1.1                  jakarta-tomcat/src/webdav/org/apache/tomcat/webdav/resources/ResourceUtils.java
  
  Index: ResourceUtils.java
  ===================================================================
   /*
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */
  
  
  package org.apache.tomcat.webdav.resources;
  
  
  import java.beans.PropertyChangeEvent;
  import java.beans.PropertyChangeListener;
  import java.beans.PropertyChangeSupport;
  import java.io.File;
  import java.io.InputStream;
  import java.io.IOException;
  import java.net.MalformedURLException;
  import java.net.URL;
  import java.util.HashMap;
  import org.apache.tomcat.webdav.util.StringManager;
  
  /**
   * Convenience base class for implementations of the <b>Resources</b>
   * interface.  It is expected that subclasses of this class will be
   * created for each flavor of document root to be supported.
   * <p>
   * Included in the basic support provided by this class is provisions
   * for caching of resources according to configurable policy properties.
   * This will be especially useful for web applications with relatively
   * small amounts of static content (such as a 100% dynamic JSP based
   * application with just a few images), as well as environments where
   * accessing the underlying resources is relatively time consuming
   * (such as a local or remote JAR file).
   *
   * @author Craig R. McClanahan
   * @version $Revision: 1.1 $ $Date: 2000/11/03 21:27:42 $
   */
  
  public class ResourceUtils {
  
      /** Find extension
       */
      public static String getExtension(String file) {
  	if (file == null)
  	    return (null);
  	int period = file.lastIndexOf(".");
  	if (period < 0)
  	    return (null);
  	String extension = file.substring(period + 1);
  	if (extension.length() < 1)
  	    return (null);
  	return extension;
      }
  
  
      /**
       * 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) {
  
  	// Normalize the slashes and add leading slash if necessary
  	String normalized = path;
  	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 "%20" in the normalized path
  	while (true) {
  	    int index = normalized.indexOf("%20");
  	    if (index < 0)
  		break;
  	    normalized = normalized.substring(0, index) + " " +
  		normalized.substring(index + 3);
          }
  
  	// 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);
  
      }
  
  
      private static final StringManager sm =
  	StringManager.getManager("org.apache.tomcat.webdav.resources");
  
      /**
       * Validate the format of the specified path, which should be context
       * relative and begin with a slash character.
       *
       * @param path Context-relative path to be validated
       *
       * @exception IllegalArgumentException if the specified path is null
       *  or does not have a valid format
       */
      public static void validate(String path) {
  	if ((path == null) || !path.startsWith("/"))
  	    throw new IllegalArgumentException
  		(sm.getString("resources.path", path));
      }
  
  }
  
  
  
  1.1                  jakarta-tomcat/src/webdav/org/apache/tomcat/webdav/resources/Resources.java
  
  Index: Resources.java
  ===================================================================
  /*
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */
  
  package org.apache.tomcat.webdav.resources;
  
  
  import java.beans.PropertyChangeListener;
  import java.io.InputStream;
  import java.net.MalformedURLException;
  import java.net.URL;
  
  
  /**
   * A <b>Resources</b> is a generic interface for the resource acquisition
   *
   * @author Craig R. McClanahan
   * @author Remy Maucherat
   * @author Costin Manolache
   * @version $Revision: 1.1 $ $Date: 2000/11/03 21:27:43 $
   */
  public interface Resources {
  
      /**
       * Return the resource located at the named path as an
       * <code>InputStream</code> object.
       * <p>
       * The data in the <code>InputStream</code> can be of any type or length.
       * The path must be specified according to the rules given in
       * <code>getResource()</code>.  This method returns <code>null</code>
       * if no resource exists at the specified path.
       * <p>
       * Meta-information such as content length and content type that is
       * available via the <code>getResource()</code> method is lost when
       * using this method.
       * <p>
       * The servlet container must implement the URL handlers and
       * <code>URLConnection</code> objects that are necessary to access
       * the resource.
       * <p>
       * This method is different from
       * <code>java.lang.Class.getResourceAsStream()</code>, which uses a
       * class loader.  This method allows servlet containers to make a
       * resource available to a servlet from any location, without using
       * a class loader.
       *
       * @param path The path to the desired resource
       */
      public InputStream getResourceAsStream(String path);
  
      /**
       * Returns true if a resource exists at the specified path,
       * where <code>path</code> would be suitable for passing as an argument to
       * <code>getResource()</code> or <code>getResourceAsStream()</code>.
       * If there is no resource at the specified location, return false.
       *
       * @param path The path to the desired resource
       */
      public boolean exists(String path);
  
      /**
       * Return the last modified date/time of the resource at the specified
       * path, where <code>path</code> would be suitable for passing as an
       * argument to <code>getResource()</code> or
       * <code>getResourceAsStream()</code>.  If there is no resource at the
       * specified location, return -1.
       *
       * @param path The path to the desired resource
       */
      public long getResourceModified(String path);
  
      /**
       * Return the creation date/time of the resource at the specified
       * path, where <code>path</code> would be suitable for passing as an
       * argument to <code>getResource()</code> or
       * <code>getResourceAsStream()</code>.  If there is no resource at the
       * specified location, return -1. If this time is unknown, the
       * implementation should return getResourceModified(path).
       *
       * @param path The path to the desired resource
       */
      public long getResourceCreated(String path);
  
      /**
       * Return the content length of the resource at the specified
       * path, where <code>path</code> would be suitable for passing as an
       * argument to <code>getResource()</code> or
       * <code>getResourceAsStream()</code>.  If the content length
       * of the resource can't be determinedof if the resource is a collection,
       * return -1. If no content is available, return 0.
       *
       * @param path The path to the desired resource
       */
      public long getResourceLength(String path);
  
      /**
       * Return true if the resource at the specified path is a collection. A
       * collection is a special type of resource which has no content but
       * contains child resources.
       *
       * @param path The path to the desired resource
       */
      public boolean isCollection(String path);
  
      /**
       * Return the children of the resource at the specified path, if any. This
       * will return null if the resource is not a collection, or if it is a
       * collection but has no children.
       *
       * @param path The path to the desired resource
       */
      public String[] getCollectionMembers(String path);
  
      /**
       * Set the content of the resource at the specified path. If the resource
       * already exists, its previous content is overwritten. If the resource
       * doesn't exist, its immediate parent collection (according to the path
       * given) exists, then its created, and the given content is associated
       * with it. Return false if either the resource is a collection, or
       * no parent collection exist.
       *
       * @param path The path to the desired resource
       * @param content InputStream to the content to be set
       */
      public boolean setResource(String path, InputStream content);
  
      /**
       * Create a collection at the specified path. A parent collection for this
       * collection must exist. Return false if a resource already exist at the
       * path specified, or if the parent collection doesn't exist.
       *
       * @param path The path to the desired resource
       */
      public boolean createCollection(String path);
  
      /**
       * Delete the specified resource. Non-empty collections cannot be deleted
       * before deleting all their member resources. Return false is deletion
       * fails because either the resource specified doesn't exist, or the
       * resource is a non-empty collection.
       *
       * @param path The path to the desired resource
       */
      public boolean deleteResource(String path);
  
  }
  
  
  
  
  
  1.1                  jakarta-tomcat/src/webdav/org/apache/tomcat/webdav/resources/ResourcesBase.java
  
  Index: ResourcesBase.java
  ===================================================================
   /*
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */
  
  
  package org.apache.tomcat.webdav.resources;
  
  
  import java.beans.PropertyChangeEvent;
  import java.beans.PropertyChangeListener;
  import java.beans.PropertyChangeSupport;
  import java.io.File;
  import java.io.InputStream;
  import java.io.IOException;
  import java.net.MalformedURLException;
  import java.net.URL;
  import java.util.HashMap;
  import org.apache.tomcat.webdav.util.StringManager;
  /**
   * Convenience base class for implementations of the <b>Resources</b>
   * interface.  It is expected that subclasses of this class will be
   * created for each flavor of document root to be supported.
   * <p>
   * Included in the basic support provided by this class is provisions
   * for caching of resources according to configurable policy properties.
   * This will be especially useful for web applications with relatively
   * small amounts of static content (such as a 100% dynamic JSP based
   * application with just a few images), as well as environments where
   * accessing the underlying resources is relatively time consuming
   * (such as a local or remote JAR file).
   *
   * @author Craig R. McClanahan
   * @version $Revision: 1.1 $ $Date: 2000/11/03 21:27:43 $
   */
  public abstract class ResourcesBase
      implements Resources, Runnable { 
  
      // ----------------------------------------------------------- Constructors
  
      /**
       * Construct a new instance of this class with default values.
       */
      public ResourcesBase() {
  	super();
      }
  
      // ----------------------------------------------------- Instance Variables
  
  
      /**
       * The interval (in seconds) at which our background task should check
       * for out-of-date cached resources, or zero for no checks.
       */
      protected int checkInterval = 0;
  
  
      /**
       * The debugging detail level for this component.
       */
      protected int debug = 0;
  
  
      /**
       * The document root for this component.
       */
      protected String docBase = null;
  
      /**
       * Should "directory" entries be expanded?
       */
      protected boolean expand = true;
  
      /**
       * The maximum number of resources to cache.
       */
      protected int maxCount = 0;
  
      /**
       * The maximum size of resources to be cached.
       */
      protected long maxSize = 0L;
  
      /**
       * The minimum size of resources to be cached.
       */
      protected long minSize = 0L;
  
      /**
       * The set of ResourceBean entries for this component,
       * keyed by the normalized context-relative resource URL.
       */
      protected HashMap resourcesCache = new HashMap();
  
      /**
       * The count of ResourceBean entries for which we have actually
       * cached data.  This can be different from the number of elements
       * in the <code>resourcesCache</code> collection.
       */
      protected int resourcesCount = 0;
  
      /**
       * The string manager for this package.
       */
      protected static final StringManager sm =
  	StringManager.getManager("org.apache.tomcat.webdav.resources");
  
  
      /**
       * Has this component been started?
       */
      protected boolean started = false;
  
      /**
       * The background thread.
       */
      protected Thread thread = null;
  
  
      /**
       * The background thread completion semaphore.
       */
      protected boolean threadDone = false;
  
  
      /**
       * The name to register for the background thread.
       */
      protected String threadName = "ResourcesBase";
  
  
      // ------------------------------------------------------------- Properties
  
  
      /**
       * Return the resource cache check interval.
       */
      public int getCheckInterval() {
  
  	return (this.checkInterval);
  
      }
  
  
      /**
       * Set the resource cache check interval.
       *
       * @param checkInterval The new check interval
       */
      public void setCheckInterval(int checkInterval) {
  
  	// Perform the property update
  	int oldCheckInterval = this.checkInterval;
  	this.checkInterval = checkInterval;
  
  	// Start or stop the background thread (if necessary)
  	if (started) {
  	    if ((oldCheckInterval > 0) && (this.checkInterval <= 0))
  		threadStop();
  	    else if ((oldCheckInterval <= 0) && (this.checkInterval > 0))
  		threadStart();
  	}
  
      }
  
  
      /**
       * Return the debugging detail level for this component.
       */
      public int getDebug() {
  
  	return (this.debug);
  
      }
  
  
      /**
       * Set the debugging detail level for this component.
       *
       * @param debug The new debugging detail level
       */
      public void setDebug(int debug) {
  	this.debug = debug;
      }
  
  
      /**
       * Return the document root for this component.
       */
      public String getDocBase() {
  	return (this.docBase);
      }
  
  
      /**
       * Set the document root for this component.
       *
       * @param docBase The new document root
       *
       * @exception IllegalArgumentException if the specified value is not
       *  supported by this implementation
       * @exception IllegalArgumentException if this would create a
       *  malformed URL
       */
      public void setDocBase(String docBase) {
  	this.docBase = docBase.toString();
  	if (debug >= 1)
  	    log("Setting docBase to '" + this.docBase + "'");
      }
  
  
      /**
       * Return the "expand directories" flag.
       */
      public boolean getExpand() {
  	return (this.expand);
      }
  
  
      /**
       * Set the "expand directories" flag.
       *
       * @param expand The new "expand directories" flag
       */
      public void setExpand(boolean expand) {
  	this.expand = expand;
      }
  
  
  
      /**
       * Return the maximum number of resources to cache.
       */
      public int getMaxCount() {
  	return (this.maxCount);
      }
  
  
      /**
       * Set the maximum number of resources to cache.
       *
       * @param maxCount The new maximum count
       */
      public void setMaxCount(int maxCount) {
  	this.maxCount = maxCount;
      }
  
  
      /**
       * Return the maximum size of resources to be cached.
       */
      public long getMaxSize() {
  	return (this.maxSize);
      }
  
  
      /**
       * Set the maximum size of resources to be cached.
       *
       * @param maxSize The new maximum size
       */
      public void setMaxSize(long maxSize) {
  	this.maxSize = maxSize;
      }
  
  
      /**
       * Return the minimum size of resources to be cached.
       */
      public long getMinSize() {
  	return (this.minSize);
      }
  
  
      /**
       * Set the minimum size of resources to be cached.
       *
       * @param minSize The new minimum size
       */
      public void setMinSize(long minSize) {
  	this.minSize = minSize;
      }
  
  
      /**
       * Prepare for the beginning of active use of the public methods of this
       * component.  This method should be called after <code>configure()</code>,
       * and before any of the public methods of the component are utilized.
       *
       * @exception IllegalStateException if this component has already been
       *  started
       * @exception LifecycleException if this component detects a fatal error
       *  that prevents this component from being used
       */
      public void start() {
  
  	// Validate and update our current component state
  	if (started)
  	    throw new RuntimeException
  		(sm.getString("resources.alreadyStarted"));
  // 	lifecycle.fireLifecycleEvent(START_EVENT, null);
  	started = true;
  
  	// Start the background expiration checking thread (if necessary)
  	if (checkInterval > 0)
  	    threadStart();
  
      }
  
  
      /**
       * Gracefully terminate the active use of the public methods of this
       * component.  This method should be the last one called on a given
       * instance of this component.
       *
       * @exception IllegalStateException if this component has not been started
       * @exception LifecycleException if this component detects a fatal error
       *  that needs to be reported
       */
      public void stop()  {
  
  	// Validate and update our current state
  	if (!started)
  	    throw new RuntimeException
  		(sm.getString("resources.notStarted"));
  
  // 	lifecycle.fireLifecycleEvent(STOP_EVENT, null);
  	started = false;
  
  	// Stop the background expiration checking thread (if necessary)
  	threadStop();
  
      }
  
  
      // ------------------------------------------------------ Protected Methods
  
  
      /**
       * Should the resource specified by our parameters be cached?
       *
       * @param name Name of the proposed resource
       * @param size Size (in bytes) of the proposed resource
       */
      protected boolean cacheable(String name, long size) {
  
  	if ((size < minSize) || (size > maxSize))
  	    return (false);
  	else if (resourcesCount >= maxCount)
  	    return (false);
  	else
  	    return (true);
  
      }
  
  
  //     /**
  //      * Return a File object representing the base directory for the
  //      * entire servlet container (i.e. the Engine container if present).
  //      */
  //     protected File engineBase() {
  // 	/*DEBUG*/ try {throw new Exception(); } catch(Exception ex) {ex.printStackTrace();}
  // 	return (new File(System.getProperty("catalina.home")));
  
  //     }
  
  
      // -------------------- Logging --------------------
  
      /**
       * Log a message on the Logger associated with our Container (if any)
       *
       * @param message Message to be logged
       */
      protected void log(String message) {
  	System.out.println("ResourceBase: " + message );
      }
  
  
      /**
       * Log a message on the Logger associated with our Container (if any)
       *
       * @param message Message to be logged
       * @param throwable Associated exception
       */
      protected void log(String message, Throwable throwable) {
  	System.out.println("ResourceBase: " + message );
  	throwable.printStackTrace();
      }
  
  
      /**
       * Scan our cached resources, looking for cases where the underlying
       * resource has been modified since we cached it.
       */
      protected void threadProcess() {
  
  	// Create a list of the cached resources we know about
  	ResourceBean entries[] = new ResourceBean[0];
  	synchronized(resourcesCache) {
  	    entries =
  		(ResourceBean[]) resourcesCache.values().toArray(entries);
  	}
  
  	// Check the last modified date on each entry
  	for (int i = 0; i < entries.length; i++) {
  	    if (entries[i].getLastModified() !=
  		getResourceModified(entries[i].getName())) {
  		synchronized (resourcesCache) {
  		    resourcesCache.remove(entries[i].getName());
  		}
  	    }
  	}
  
      }
  
  
      /**
       * Start the background thread that will periodically check for
       * session timeouts.
       */
      protected void threadStart() {
  
  	if (thread != null)
  	    return;
  
  	threadDone = false;
  	threadName = "ResourcesBase[" + docBase + "]";
  	thread = new Thread(this, threadName);
  	thread.setDaemon(true);
  	thread.start();
  
      }
  
  
      /**
       * Stop the background thread that is periodically checking for
       * session timeouts.
       */
      protected void threadStop() {
  
  	if (thread == null)
  	    return;
  
  	threadDone = true;
  	thread.interrupt();
  	try {
  	    thread.join();
  	} catch (InterruptedException e) {
  	    ;
  	}
  
  	thread = null;
  
      }
  
      // ------------------------------------------------------ Background Thread
  
  
      /**
       * The background thread that checks for session timeouts and shutdown.
       */
      public void run() {
  
  	// Loop until the termination semaphore is set
  	while (!threadDone) {
  	    try {
  		Thread.sleep(checkInterval * 1000L);
  	    } catch (InterruptedException e) {
  		;
  	    }
  	    threadProcess();
  	}
  
      }
  
  
  }
  
  
  
  1.1                  jakarta-tomcat/src/webdav/org/apache/tomcat/webdav/util/DOMWriter.java
  
  Index: DOMWriter.java
  ===================================================================
  /*
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999 The Apache Software Foundation.  All rights 
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer. 
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:  
   *       "This product includes software developed by the 
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written 
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */ 
  
  package org.apache.tomcat.webdav.util;
  
  import java.io.*;
  import org.w3c.dom.Attr;
  import org.w3c.dom.Document;
  import org.w3c.dom.NamedNodeMap;
  import org.w3c.dom.Node;
  import org.w3c.dom.NodeList;
  import org.xml.sax.InputSource;
  
  /**
   * A sample DOM writer. This sample program illustrates how to
   * traverse a DOM tree in order to print a document that is parsed.
   */
  public class DOMWriter {
  
     //
     // Data
     //
  
     /** Default Encoding */
     private static  String
     PRINTWRITER_ENCODING = "UTF8";
  
     private static String MIME2JAVA_ENCODINGS[] =
      { "Default", "UTF-8", "US-ASCII", "ISO-8859-1", "ISO-8859-2", "ISO-8859-3", "ISO-8859-4", 
        "ISO-8859-5", "ISO-8859-6", "ISO-8859-7", "ISO-8859-8", "ISO-8859-9", "ISO-2022-JP",
        "SHIFT_JIS", "EUC-JP","GB2312", "BIG5", "EUC-KR", "ISO-2022-KR", "KOI8-R", "EBCDIC-CP-US", 
        "EBCDIC-CP-CA", "EBCDIC-CP-NL", "EBCDIC-CP-DK", "EBCDIC-CP-NO", "EBCDIC-CP-FI", "EBCDIC-CP-SE",
        "EBCDIC-CP-IT", "EBCDIC-CP-ES", "EBCDIC-CP-GB", "EBCDIC-CP-FR", "EBCDIC-CP-AR1", 
        "EBCDIC-CP-HE", "EBCDIC-CP-CH", "EBCDIC-CP-ROECE","EBCDIC-CP-YU",  
        "EBCDIC-CP-IS", "EBCDIC-CP-AR2", "UTF-16"
      };
  
  
     /** Print writer. */
     protected PrintWriter out;
  
     /** Canonical output. */
     protected boolean canonical;
  
  
     public DOMWriter(String encoding, boolean canonical)              
     throws UnsupportedEncodingException {
        out = new PrintWriter(new OutputStreamWriter(System.out, encoding));
        this.canonical = canonical;
     } // <init>(String,boolean)
  
     //
     // Constructors
     //
  
     /** Default constructor. */
     public DOMWriter(boolean canonical) throws UnsupportedEncodingException {
        this( getWriterEncoding(), canonical);
     }
  
      public DOMWriter(Writer writer, boolean canonical) {
  	out = new PrintWriter(writer);
  	this.canonical = canonical;	
      }
  
     public static String getWriterEncoding( ) {
        return (PRINTWRITER_ENCODING);
     }// getWriterEncoding 
  
     public static void  setWriterEncoding( String encoding ) {
        if( encoding.equalsIgnoreCase( "DEFAULT" ) )
           PRINTWRITER_ENCODING  = "UTF8";
        else if( encoding.equalsIgnoreCase( "UTF-16" ) )
           PRINTWRITER_ENCODING  = "Unicode";
        else
           PRINTWRITER_ENCODING = MIME2Java.convert( encoding ); 
     }// setWriterEncoding 
  
  
     public static boolean isValidJavaEncoding( String encoding ) {
        for ( int i = 0; i < MIME2JAVA_ENCODINGS.length; i++ )
           if ( encoding.equals( MIME2JAVA_ENCODINGS[i] ) )
              return (true);
  
        return (false);
     }// isValidJavaEncoding 
  
  
     /** Prints the specified node, recursively. */
     public void print(Node node) {
  
        // is there anything to do?
        if ( node == null ) {
           return;
        }
  
        int type = node.getNodeType();
        switch ( type ) {
           // print document
           case Node.DOCUMENT_NODE: {
                 if ( !canonical ) {
                    String  Encoding = this.getWriterEncoding();
                    if( Encoding.equalsIgnoreCase( "DEFAULT" ) )
                       Encoding = "UTF-8";
                    else if( Encoding.equalsIgnoreCase( "Unicode" ) )
                       Encoding = "UTF-16";
                    else 
                       Encoding = MIME2Java.reverse( Encoding );
  
                    out.println("<?xml version=\"1.0\" encoding=\""+
                             Encoding + "\"?>");
                 }
                 print(((Document)node).getDocumentElement());
                 out.flush();
                 break;
              }
  
              // print element with attributes
           case Node.ELEMENT_NODE: {
                 out.print('<');
                 out.print(node.getNodeName());
                 Attr attrs[] = sortAttributes(node.getAttributes());
                 for ( int i = 0; i < attrs.length; i++ ) {
                    Attr attr = attrs[i];
                    out.print(' ');
                    out.print(attr.getNodeName());
                    out.print("=\"");
                    out.print(normalize(attr.getNodeValue()));
                    out.print('"');
                 }
                 out.print('>');
                 NodeList children = node.getChildNodes();
                 if ( children != null ) {
                    int len = children.getLength();
                    for ( int i = 0; i < len; i++ ) {
                       print(children.item(i));
                    }
                 }
                 break;
              }
  
              // handle entity reference nodes
           case Node.ENTITY_REFERENCE_NODE: {
                 if ( canonical ) {
                    NodeList children = node.getChildNodes();
                    if ( children != null ) {
                       int len = children.getLength();
                       for ( int i = 0; i < len; i++ ) {
                          print(children.item(i));
                       }
                    }
                 } else {
                    out.print('&');
                    out.print(node.getNodeName());
                    out.print(';');
                 }
                 break;
              }
  
              // print cdata sections
           case Node.CDATA_SECTION_NODE: {
                 if ( canonical ) {
                    out.print(normalize(node.getNodeValue()));
                 } else {
                    out.print("<![CDATA[");
                    out.print(node.getNodeValue());
                    out.print("]]>");
                 }
                 break;
              }
  
              // print text
           case Node.TEXT_NODE: {
                 out.print(normalize(node.getNodeValue()));
                 break;
              }
  
              // print processing instruction
           case Node.PROCESSING_INSTRUCTION_NODE: {
                 out.print("<?");
                 out.print(node.getNodeName());
                 String data = node.getNodeValue();
                 if ( data != null && data.length() > 0 ) {
                    out.print(' ');
                    out.print(data);
                 }
                 out.print("?>");
                 break;
              }
        }
  
        if ( type == Node.ELEMENT_NODE ) {
           out.print("</");
           out.print(node.getNodeName());
           out.print('>');
        }
  
        out.flush();
  
     } // print(Node)
  
     /** Returns a sorted list of attributes. */
     protected Attr[] sortAttributes(NamedNodeMap attrs) {
  
        int len = (attrs != null) ? attrs.getLength() : 0;
        Attr array[] = new Attr[len];
        for ( int i = 0; i < len; i++ ) {
           array[i] = (Attr)attrs.item(i);
        }
        for ( int i = 0; i < len - 1; i++ ) {
           String name  = array[i].getNodeName();
           int    index = i;
           for ( int j = i + 1; j < len; j++ ) {
              String curName = array[j].getNodeName();
              if ( curName.compareTo(name) < 0 ) {
                 name  = curName;
                 index = j;
              }
           }
           if ( index != i ) {
              Attr temp    = array[i];
              array[i]     = array[index];
              array[index] = temp;
           }
        }
  
        return (array);
  
     } // sortAttributes(NamedNodeMap):Attr[]
  
  
     /** Normalizes the given string. */
     protected String normalize(String s) {
        StringBuffer str = new StringBuffer();
  
        int len = (s != null) ? s.length() : 0;
        for ( int i = 0; i < len; i++ ) {
           char ch = s.charAt(i);
           switch ( ch ) {
              case '<': {
                    str.append("&lt;");
                    break;
                 }
              case '>': {
                    str.append("&gt;");
                    break;
                 }
              case '&': {
                    str.append("&amp;");
                    break;
                 }
              case '"': {
                    str.append("&quot;");
                    break;
                 }
              case '\r':
              case '\n': {
                    if ( canonical ) {
                       str.append("&#");
                       str.append(Integer.toString(ch));
                       str.append(';');
                       break;
                    }
                    // else, default append char
                 }
              default: {
                    str.append(ch);
                 }
           }
        }
  
        return (str.toString());
  
     } // normalize(String):String
  
     private static void printValidJavaEncoding() {
        System.err.println( "    ENCODINGS:" );
        System.err.print( "   " );
        for( int i = 0;
                       i < MIME2JAVA_ENCODINGS.length; i++) {
           System.err.print( MIME2JAVA_ENCODINGS[i] + " " );
        if( (i % 7 ) == 0 ){
           System.err.println();
           System.err.print( "   " );
           }
        }
  
     } // printJavaEncoding()            
  
  } 
  
  
  
  1.1                  jakarta-tomcat/src/webdav/org/apache/tomcat/webdav/util/MD5Encoder.java
  
  Index: MD5Encoder.java
  ===================================================================
  /*
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999 The Apache Software Foundation.  All rights 
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer. 
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:  
   *       "This product includes software developed by the 
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written 
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */ 
  
  
  package org.apache.tomcat.webdav.util;
  
  
  /**
   * Encode an MD5 digest into a String.
   * <p>
   * The 128 bit MD5 hash is converted into a 32 character long String.
   * Each character of the String is the hexadecimal representation of 4 bits
   * of the digest.
   *
   * @author Remy Maucherat
   * @version $Revision: 1.1 $ $Date: 2000/11/03 21:27:47 $
   */
  public final class MD5Encoder {
  
  
      // ----------------------------------------------------- Instance Variables
  
  
      private static final char[] hexadecimal = 
      {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 
       'a', 'b', 'c', 'd', 'e', 'f'};
  
  
      // --------------------------------------------------------- Public Methods
  
  
      /**
       * Encodes the 128 bit (16 bytes) MD5 into a 32 character String.
       *
       * @param binaryData Array containing the digest
       * @return Encoded MD5, or null if encoding failed
       */
      public String encode( byte[] binaryData ) {
          
          if (binaryData.length != 16)
              return null;
  
          char[] buffer = new char[32];
          
          for (int i=0; i<16; i++) {
              int low = (int) (binaryData[i] & 0x0f);
              int high = (int) ((binaryData[i] & 0xf0) >> 4);
              buffer[i*2] = hexadecimal[high];
              buffer[i*2 + 1] = hexadecimal[low];
          }
  
          return new String(buffer);
  
      }
  
  
  }
  
  
  
  
  1.1                  jakarta-tomcat/src/webdav/org/apache/tomcat/webdav/util/MIME2Java.java
  
  Index: MIME2Java.java
  ===================================================================
  /*
   * The Apache Software License, Version 1.1
   *
   *
   * Copyright (c) 1999 The Apache Software Foundation.  All rights 
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer. 
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution,
   *    if any, must include the following acknowledgment:  
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgment may appear in the software itself,
   *    if and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "Xerces" and "Apache Software Foundation" must
   *    not be used to endorse or promote products derived from this
   *    software without prior written permission. For written 
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache",
   *    nor may "Apache" appear in their name, without prior written
   *    permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation and was
   * originally based on software copyright (c) 1999, International
   * Business Machines, Inc., http://www.apache.org.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  
  package org.apache.tomcat.webdav.util;
  
  import java.util.*;
  
  /**
   * MIME2Java is a convenience class which handles conversions between MIME charset names
   * and Java encoding names.
   * <p>The supported XML encodings are the intersection of XML-supported code sets and those 
   * supported in JDK 1.1.
   * <p>MIME charset names are used on <var>xmlEncoding</var> parameters to methods such
   * as <code>TXDocument#setEncoding</code> and <code>DTD#setEncoding</code>.
   * <p>Java encoding names are used on <var>encoding</var> parameters to
   * methods such as <code>TXDocument#printWithFormat</code> and <code>DTD#printExternal</code>. 
   * <P>
   * <TABLE BORDER="0" WIDTH="100%">
   *  <TR>
   *      <TD WIDTH="33%">
   *          <P ALIGN="CENTER"><B>Common Name</B>
   *      </TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER"><B>Use this name in XML files</B>
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER"><B>Name Type</B>
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER"><B>Xerces converts to this Java Encoder Name</B>
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">8 bit Unicode</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">UTF-8
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">IANA
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">UTF8
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">ISO Latin 1</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ISO-8859-1
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">MIME
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">ISO-8859-1
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">ISO Latin 2</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ISO-8859-2
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">MIME
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">ISO-8859-2
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">ISO Latin 3</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ISO-8859-3
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">MIME
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">ISO-8859-3
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">ISO Latin 4</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ISO-8859-4
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">MIME
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">ISO-8859-4
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">ISO Latin Cyrillic</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ISO-8859-5
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">MIME
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">ISO-8859-5
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">ISO Latin Arabic</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ISO-8859-6
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">MIME
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">ISO-8859-6
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">ISO Latin Greek</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ISO-8859-7
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">MIME
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">ISO-8859-7
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">ISO Latin Hebrew</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ISO-8859-8
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">MIME
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">ISO-8859-8
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">ISO Latin 5</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ISO-8859-9
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">MIME
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">ISO-8859-9
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">EBCDIC: US</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ebcdic-cp-us
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">IANA
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">cp037
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">EBCDIC: Canada</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ebcdic-cp-ca
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">IANA
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">cp037
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">EBCDIC: Netherlands</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ebcdic-cp-nl
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">IANA
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">cp037
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">EBCDIC: Denmark</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ebcdic-cp-dk
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">IANA
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">cp277
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">EBCDIC: Norway</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ebcdic-cp-no
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">IANA
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">cp277
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">EBCDIC: Finland</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ebcdic-cp-fi
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">IANA
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">cp278
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">EBCDIC: Sweden</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ebcdic-cp-se
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">IANA
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">cp278
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">EBCDIC: Italy</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ebcdic-cp-it
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">IANA
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">cp280
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">EBCDIC: Spain, Latin America</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ebcdic-cp-es
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">IANA
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">cp284
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">EBCDIC: Great Britain</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ebcdic-cp-gb
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">IANA
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">cp285
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">EBCDIC: France</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ebcdic-cp-fr
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">IANA
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">cp297
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">EBCDIC: Arabic</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ebcdic-cp-ar1
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">IANA
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">cp420
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">EBCDIC: Hebrew</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ebcdic-cp-he
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">IANA
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">cp424
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">EBCDIC: Switzerland</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ebcdic-cp-ch
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">IANA
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">cp500
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">EBCDIC: Roece</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ebcdic-cp-roece
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">IANA
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">cp870
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">EBCDIC: Yogoslavia</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ebcdic-cp-yu
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">IANA
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">cp870
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">EBCDIC: Iceland</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ebcdic-cp-is
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">IANA
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">cp871
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">EBCDIC: Urdu</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">ebcdic-cp-ar2
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">IANA
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">cp918
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">Chinese for PRC, mixed 1/2 byte</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">gb2312
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">MIME
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">GB2312
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">Extended Unix Code, packed for Japanese</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">euc-jp
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">MIME
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">eucjis
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">Japanese: iso-2022-jp</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">iso-2020-jp
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">MIME
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">JIS
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">Japanese: Shift JIS</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">Shift_JIS
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">MIME
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">SJIS
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">Chinese: Big5</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">Big5
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">MIME
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">Big5
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">Extended Unix Code, packed for Korean</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">euc-kr
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">MIME
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">iso2022kr
   *      </TD>
   *  </TR>
   *  <TR>
   *      <TD WIDTH="33%">Cyrillic</TD>
   *      <TD WIDTH="15%">
   *          <P ALIGN="CENTER">koi8-r
   *      </TD>
   *      <TD WIDTH="12%">
   *          <P ALIGN="CENTER">MIME
   *      </TD>
   *      <TD WIDTH="31%">
   *          <P ALIGN="CENTER">koi8-r
   *      </TD>
   *  </TR>
   * </TABLE>
   * 
   * @version
   * @author TAMURA Kent &lt;kent@trl.ibm.co.jp&gt;
   */
  public class MIME2Java {
      
      static private Hashtable s_enchash;
      static private Hashtable s_revhash;
      
      static {
          s_enchash = new Hashtable();
          //    <preferred MIME name>, <Java encoding name>
          s_enchash.put("UTF-8", "UTF8");
          s_enchash.put("US-ASCII",        "8859_1");    // ?
          s_enchash.put("ISO-8859-1",      "8859_1");
          s_enchash.put("ISO-8859-2",      "8859_2");
          s_enchash.put("ISO-8859-3",      "8859_3");
          s_enchash.put("ISO-8859-4",      "8859_4");
          s_enchash.put("ISO-8859-5",      "8859_5");
          s_enchash.put("ISO-8859-6",      "8859_6");
          s_enchash.put("ISO-8859-7",      "8859_7");
          s_enchash.put("ISO-8859-8",      "8859_8");
          s_enchash.put("ISO-8859-9",      "8859_9");
          s_enchash.put("ISO-2022-JP",     "JIS");
          s_enchash.put("SHIFT_JIS",       "SJIS");
          s_enchash.put("EUC-JP",          "EUCJIS");
          s_enchash.put("GB2312",          "GB2312");
          s_enchash.put("BIG5",            "Big5");
          s_enchash.put("EUC-KR",          "KSC5601");
          s_enchash.put("ISO-2022-KR",     "ISO2022KR");
          s_enchash.put("KOI8-R",          "KOI8_R");
  
          s_enchash.put("EBCDIC-CP-US",    "CP037");
          s_enchash.put("EBCDIC-CP-CA",    "CP037");
          s_enchash.put("EBCDIC-CP-NL",    "CP037");
          s_enchash.put("EBCDIC-CP-DK",    "CP277");
          s_enchash.put("EBCDIC-CP-NO",    "CP277");
          s_enchash.put("EBCDIC-CP-FI",    "CP278");
          s_enchash.put("EBCDIC-CP-SE",    "CP278");
          s_enchash.put("EBCDIC-CP-IT",    "CP280");
          s_enchash.put("EBCDIC-CP-ES",    "CP284");
          s_enchash.put("EBCDIC-CP-GB",    "CP285");
          s_enchash.put("EBCDIC-CP-FR",    "CP297");
          s_enchash.put("EBCDIC-CP-AR1",   "CP420");
          s_enchash.put("EBCDIC-CP-HE",    "CP424");
          s_enchash.put("EBCDIC-CP-CH",    "CP500");
          s_enchash.put("EBCDIC-CP-ROECE", "CP870");
          s_enchash.put("EBCDIC-CP-YU",    "CP870");
          s_enchash.put("EBCDIC-CP-IS",    "CP871");
          s_enchash.put("EBCDIC-CP-AR2",   "CP918");
  
                                                  // j:CNS11643 -> EUC-TW?
                                                  // ISO-2022-CN? ISO-2022-CN-EXT?
                                                  
          s_revhash = new Hashtable();
          //    <Java encoding name>, <preferred MIME name>
          s_revhash.put("UTF8", "UTF-8");
          //s_revhash.put("8859_1", "US-ASCII");    // ?
          s_revhash.put("8859_1", "ISO-8859-1");
          s_revhash.put("8859_2", "ISO-8859-2");
          s_revhash.put("8859_3", "ISO-8859-3");
          s_revhash.put("8859_4", "ISO-8859-4");
          s_revhash.put("8859_5", "ISO-8859-5");
          s_revhash.put("8859_6", "ISO-8859-6");
          s_revhash.put("8859_7", "ISO-8859-7");
          s_revhash.put("8859_8", "ISO-8859-8");
          s_revhash.put("8859_9", "ISO-8859-9");
          s_revhash.put("JIS", "ISO-2022-JP");
          s_revhash.put("SJIS", "Shift_JIS");
          s_revhash.put("EUCJIS", "EUC-JP");
          s_revhash.put("GB2312", "GB2312");
          s_revhash.put("BIG5", "Big5");
          s_revhash.put("KSC5601", "EUC-KR");
          s_revhash.put("ISO2022KR", "ISO-2022-KR");
          s_revhash.put("KOI8_R", "KOI8-R");
  
          s_revhash.put("CP037", "EBCDIC-CP-US");
          s_revhash.put("CP037", "EBCDIC-CP-CA");
          s_revhash.put("CP037", "EBCDIC-CP-NL");
          s_revhash.put("CP277", "EBCDIC-CP-DK");
          s_revhash.put("CP277", "EBCDIC-CP-NO");
          s_revhash.put("CP278", "EBCDIC-CP-FI");
          s_revhash.put("CP278", "EBCDIC-CP-SE");
          s_revhash.put("CP280", "EBCDIC-CP-IT");
          s_revhash.put("CP284", "EBCDIC-CP-ES");
          s_revhash.put("CP285", "EBCDIC-CP-GB");
          s_revhash.put("CP297", "EBCDIC-CP-FR");
          s_revhash.put("CP420", "EBCDIC-CP-AR1");
          s_revhash.put("CP424", "EBCDIC-CP-HE");
          s_revhash.put("CP500", "EBCDIC-CP-CH");
          s_revhash.put("CP870", "EBCDIC-CP-ROECE");
          s_revhash.put("CP870", "EBCDIC-CP-YU");
          s_revhash.put("CP871", "EBCDIC-CP-IS");
          s_revhash.put("CP918", "EBCDIC-CP-AR2");
      }
  
      private MIME2Java() {
      }
  
      /**
       * Convert a MIME charset name, also known as an XML encoding name, to a Java encoding name.
       * @param   mimeCharsetName Case insensitive MIME charset name: <code>UTF-8, US-ASCII, ISO-8859-1,
       *                          ISO-8859-2, ISO-8859-3, ISO-8859-4, ISO-8859-5, ISO-8859-6,
       *                          ISO-8859-7, ISO-8859-8, ISO-8859-9, ISO-2022-JP, Shift_JIS, 
       *                          EUC-JP, GB2312, Big5, EUC-KR, ISO-2022-KR, KOI8-R,
       *                          EBCDIC-CP-US, EBCDIC-CP-CA, EBCDIC-CP-NL, EBCDIC-CP-DK,
       *                          EBCDIC-CP-NO, EBCDIC-CP-FI, EBCDIC-CP-SE, EBCDIC-CP-IT,
       *                          EBCDIC-CP-ES, EBCDIC-CP-GB, EBCDIC-CP-FR, EBCDIC-CP-AR1,
       *                          EBCDIC-CP-HE, EBCDIC-CP-CH, EBCDIC-CP-ROECE, EBCDIC-CP-YU,
       *                          EBCDIC-CP-IS and EBCDIC-CP-AR2</code>.
       * @return                  Java encoding name, or <var>null</var> if <var>mimeCharsetName</var>
       *                          is unknown.
       * @see #reverse
       */
      public static String convert(String mimeCharsetName) {
          return (String)s_enchash.get(mimeCharsetName.toUpperCase());
      }
  
      /**
       * Convert a Java encoding name to MIME charset name.
       * Available values of <i>encoding</i> are "UTF8", "8859_1", "8859_2", "8859_3", "8859_4",
       * "8859_5", "8859_6", "8859_7", "8859_8", "8859_9", "JIS", "SJIS", "EUCJIS",
       * "GB2312", "BIG5", "KSC5601", "ISO2022KR",  "KOI8_R", "CP037", "CP277", "CP278",
       * "CP280", "CP284", "CP285", "CP297", "CP420", "CP424", "CP500", "CP870", "CP871" and "CP918".
       * @param   encoding    Case insensitive Java encoding name: <code>UTF8, 8859_1, 8859_2, 8859_3,
       *                      8859_4, 8859_5, 8859_6, 8859_7, 8859_8, 8859_9, JIS, SJIS, EUCJIS,
       *                      GB2312, BIG5, KSC5601, ISO2022KR, KOI8_R, CP037, CP277, CP278,
       *                      CP280, CP284, CP285, CP297, CP420, CP424, CP500, CP870, CP871 
       *                      and CP918</code>.
       * @return              MIME charset name, or <var>null</var> if <var>encoding</var> is unknown.
       * @see #convert
       */
      public static String reverse(String encoding) {
          return (String)s_revhash.get(encoding.toUpperCase());
      }
  }
  
  
  
  1.1                  jakarta-tomcat/src/webdav/org/apache/tomcat/webdav/util/StringManager.java
  
  Index: StringManager.java
  ===================================================================
  /*
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999 The Apache Software Foundation.  All rights 
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer. 
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:  
   *       "This product includes software developed by the 
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written 
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */ 
  
  
  package org.apache.tomcat.webdav.util;
  
  import java.text.MessageFormat;
  import java.util.Hashtable;
  import java.util.Locale;
  import java.util.MissingResourceException;
  import java.util.ResourceBundle;
  
  /**
   * An internationalization / localization helper class which reduces
   * the bother of handling ResourceBundles and takes care of the
   * common cases of message formating which otherwise require the
   * creation of Object arrays and such.
   *
   * <p>The StringManager operates on a package basis. One StringManager
   * per package can be created and accessed via the getManager method
   * call.
   *
   * <p>The StringManager will look for a ResourceBundle named by
   * the package name given plus the suffix of "LocalStrings". In
   * practice, this means that the localized information will be contained
   * in a LocalStrings.properties file located in the package
   * directory of the classpath.
   *
   * <p>Please see the documentation for java.util.ResourceBundle for
   * more information.
   *
   * @author James Duncan Davidson [duncan@eng.sun.com]
   * @author James Todd [gonzo@eng.sun.com]
   */
  
  public class StringManager {
  
      /**
       * The ResourceBundle for this StringManager.
       */
      
      private ResourceBundle bundle;
  
      /**
       * Creates a new StringManager for a given package. This is a
       * private method and all access to it is arbitrated by the
       * static getManager method call so that only one StringManager
       * per package will be created.
       *
       * @param packageName Name of package to create StringManager for.
       */
  
      private StringManager(String packageName) {
  	String bundleName = packageName + ".LocalStrings";
  	bundle = ResourceBundle.getBundle(bundleName);
      }
  
      /**
       * Get a string from the underlying resource bundle.
       *
       * @param key 
       */
      
      public String getString(String key) {
          if (key == null) {
              String msg = "key is null";
  
              throw new NullPointerException(msg);
          }
  
          String str = null;
  
          try {
  	    str = bundle.getString(key);
          } catch (MissingResourceException mre) {
              str = "Cannot find message associated with key '" + key + "'";
          }
  
          return str;
      }
  
      /**
       * Get a string from the underlying resource bundle and format
       * it with the given set of arguments.
       *
       * @param key
       * @param args
       */
  
      public String getString(String key, Object[] args) {
  	String iString = null;
          String value = getString(key);
  
  	// this check for the runtime exception is some pre 1.1.6
  	// VM's don't do an automatic toString() on the passed in
  	// objects and barf out
  	
  	try {
              // ensure the arguments are not null so pre 1.2 VM's don't barf
              Object nonNullArgs[] = args;
              for (int i=0; i<args.length; i++) {
  		if (args[i] == null) {
  		    if (nonNullArgs==args) nonNullArgs=(Object[])args.clone();
  		    nonNullArgs[i] = "null";
  		}
  	    }
   
              iString = MessageFormat.format(value, nonNullArgs);
  	} catch (IllegalArgumentException iae) {
  	    StringBuffer buf = new StringBuffer();
  	    buf.append(value);
  	    for (int i = 0; i < args.length; i++) {
  		buf.append(" arg[" + i + "]=" + args[i]);
  	    }
  	    iString = buf.toString();
  	}
  	return iString;
      }
  
      /**
       * Get a string from the underlying resource bundle and format it
       * with the given object argument. This argument can of course be
       * a String object.
       *
       * @param key
       * @param arg
       */
  
      public String getString(String key, Object arg) {
  	Object[] args = new Object[] {arg};
  	return getString(key, args);
      }
  
      /**
       * Get a string from the underlying resource bundle and format it
       * with the given object arguments. These arguments can of course
       * be String objects.
       *
       * @param key
       * @param arg1
       * @param arg2
       */
  
      public String getString(String key, Object arg1, Object arg2) {
  	Object[] args = new Object[] {arg1, arg2};
  	return getString(key, args);
      }
      
      /**
       * Get a string from the underlying resource bundle and format it
       * with the given object arguments. These arguments can of course
       * be String objects.
       *
       * @param key
       * @param arg1
       * @param arg2
       * @param arg3
       */
  
      public String getString(String key, Object arg1, Object arg2,
  			    Object arg3) {
  	Object[] args = new Object[] {arg1, arg2, arg3};
  	return getString(key, args);
      }
      
      /**
       * Get a string from the underlying resource bundle and format it
       * with the given object arguments. These arguments can of course
       * be String objects.
       *
       * @param key
       * @param arg1
       * @param arg2
       * @param arg3
       * @param arg4
       */
  
      public String getString(String key, Object arg1, Object arg2,
  			    Object arg3, Object arg4) {
  	Object[] args = new Object[] {arg1, arg2, arg3, arg4};
  	return getString(key, args);
      }   
      // --------------------------------------------------------------
      // STATIC SUPPORT METHODS
      // --------------------------------------------------------------
  
      private static Hashtable managers = new Hashtable();
  
      /**
       * Get the StringManager for a particular package. If a manager for
       * a package already exists, it will be reused, else a new
       * StringManager will be created and returned.
       *
       * @param packageName
       */
  
      public synchronized static StringManager getManager(String packageName) {
  	StringManager mgr = (StringManager)managers.get(packageName);
  	if (mgr == null) {
  	    mgr = new StringManager(packageName);
  	    managers.put(packageName, mgr);
  	}
  	return mgr;
      }
  }
  
  
  
  1.1                  jakarta-tomcat/src/webdav/org/apache/tomcat/webdav/util/XMLWriter.java
  
  Index: XMLWriter.java
  ===================================================================
  /*
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999 The Apache Software Foundation.  All rights 
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer. 
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:  
   *       "This product includes software developed by the 
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written 
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */ 
  
  package org.apache.tomcat.webdav.util;
  
  /**
   * XMLWriter helper class.
   * 
   * @author Remy Maucherat
   */
  public class XMLWriter {
      
      
      // -------------------------------------------------------------- Constants
      
      
      /**
       * Opening tag.
       */
      public static final int OPENING = 0;
      
      
      /**
       * Closing tag.
       */
      public static final int CLOSING = 1;
      
      
      /**
       * Element with no content.
       */
      public static final int NO_CONTENT = 2;
      
      
      // ----------------------------------------------------- Instance Variables
      
      
      /**
       * Buffer.
       */
      protected StringBuffer buffer;
      
      
      // ----------------------------------------------------------- Constructors
      
      
      /**
       * Constructor.
       */
      public XMLWriter() {
          buffer = new StringBuffer();
      }
      
      
      // --------------------------------------------------------- Public Methods
      
      
      /**
       * Retrieve generated XML.
       * 
       * @return String containing the generated XML
       */
      public String toString() {
          return buffer.toString();
      }
      
      
      /**
       * Write property to the XML.
       * 
       * @param namespace Namespace
       * @param namespaceInfo Namespace info
       * @param name Property name
       * @param value Property value
       */
      public void writeProperty(String namespace, String namespaceInfo, 
                                String name, String value) {
          writeElement(namespace, namespaceInfo, name, OPENING);
          buffer.append(value);
          writeElement(namespace, namespaceInfo, name, CLOSING);
      }
      
      
      /**
       * Write property to the XML.
       * 
       * @param namespace Namespace
       * @param name Property name
       * @param value Property value
       */
      public void writeProperty(String namespace, String name, String value) {
          writeElement(namespace, name, OPENING);
          buffer.append(value);
          writeElement(namespace, name, CLOSING);
      }
      
      
      /**
       * Write property to the XML.
       * 
       * @param namespace Namespace
       * @param name Property name
       */
      public void writeProperty(String namespace, String name) {
          writeElement(namespace, name, NO_CONTENT);
      }
      
      
      /**
       * Write an element.
       * 
       * @param name Element name
       * @param namespace Namespace abbreviation
       * @param type Element type
       */
      public void writeElement(String namespace, String name, int type) {
          writeElement(namespace, null, name, type);
      }
      
      
      /**
       * Write an element.
       * 
       * @param namespace Namespace abbreviation
       * @param namespaceInfo Namespace info
       * @param name Element name
       * @param type Element type
       */
      public void writeElement(String namespace, String namespaceInfo, 
                               String name, int type) {
          if ((namespace != null) && (namespace.length() > 0)) {
              switch (type) {
              case OPENING:
                  if (namespaceInfo != null) {
                      buffer.append("<" + namespace + ":" + name + " xmlns:" 
                                    + namespace + "=\"" 
                                    + namespaceInfo + "\">");
                  } else {
                      buffer.append("<" + namespace + ":" + name + ">");
                  }
                  break;
              case CLOSING:
                  buffer.append("</" + namespace + ":" + name + ">\n");
                  break;
              case NO_CONTENT:
              default:
                  if (namespaceInfo != null) {
                      buffer.append("<" + namespace + ":" + name + " xmlns:" 
                                    + namespace + "=\"" 
                                    + namespaceInfo + "\"/>");
                  } else {
                      buffer.append("<" + namespace + ":" + name + "/>");
                  }
                  break;
              }
          } else {
              switch (type) {
              case OPENING:
                  buffer.append("<" + name + ">");
                  break;
              case CLOSING:
                  buffer.append("</" + name + ">\n");
                  break;
              case NO_CONTENT:
              default:
                  buffer.append("<" + name + "/>");
                  break;
              }
          }
      }
      
      
      /**
       * Write text.
       * 
       * @param text Text to append
       */
      public void writeText(String text) {
          buffer.append(text);
      }
      
      
      /**
       * Write XML Header.
       */
      public void writeXMLHeader() {
          buffer.append("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
      }
      
      
  }