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 [EMAIL PROTECTED] * * 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 [EMAIL PROTECTED] * * 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 [EMAIL PROTECTED] * * 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\"> \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(" "); 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\"> </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 [EMAIL PROTECTED] * * 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 [EMAIL PROTECTED] * * 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 [EMAIL PROTECTED] * * 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 [EMAIL PROTECTED] * * 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 [EMAIL PROTECTED] * * 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 [EMAIL PROTECTED] * * 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 [EMAIL PROTECTED] * * 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("<"); break; } case '>': { str.append(">"); break; } case '&': { str.append("&"); break; } case '"': { str.append("""); 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 [EMAIL PROTECTED] * * 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 [EMAIL PROTECTED] * * 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 <[EMAIL PROTECTED]> */ 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 [EMAIL PROTECTED] * * 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 [[EMAIL PROTECTED]] * @author James Todd [[EMAIL PROTECTED]] */ 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 [EMAIL PROTECTED] * * 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"); } } --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]