All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.catalina.servlets.WebdavServlet Maven / Gradle / Ivy

There is a newer version: 7.2024.1.Alpha1
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2016 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 *
 *
 * This file incorporates work covered by the following copyright and
 * permission notice:
 *
 * Copyright 2004 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// Portions Copyright [2017-2021] [Payara Foundation and/or its affiliates]

package org.apache.catalina.servlets;

import org.apache.catalina.LogFacade;
import org.apache.catalina.util.DOMWriter;
import org.apache.catalina.util.MD5Encoder;
import org.apache.catalina.util.RequestUtil;
import org.apache.catalina.util.XMLWriter;
import org.apache.naming.resources.CacheEntry;
import org.apache.naming.resources.Resource;
import org.apache.naming.resources.ResourceAttributes;
import org.glassfish.grizzly.http.util.FastHttpDateFormat;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.naming.NameClassPair;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;


/**
 * Servlet which adds support for WebDAV level 2. All the basic HTTP requests
 * are handled by the DefaultServlet. The WebDAVServlet must not be used as the
 * default servlet (ie mapped to '/') as it will not work in this configuration.
 * To enable WebDAV for a context add the following to web.xml:
* <servlet>
* <servlet-name>webdav</servlet-name>
* <servlet-class>org.apache.catalina.servlets.WebdavServlet</servlet-class>
* <init-param>
* <param-name>debug</param-name>
* <param-value>0</param-value>
* </init-param>
* <init-param>
* <param-name>listings</param-name>
* <param-value>true</param-value>
* </init-param>
* </servlet>
* <servlet-mapping>
* <servlet-name>webdav</servlet-name>
* <url-pattern>/*</url-pattern>
* </servlet-mapping> *
*

* This will enable read only access. To enable read-write access add:
* * <init-param>
* <param-name>readonly</param-name>
* <param-value>false</param-value>
* </init-param>
*
*

* To make the content editable via a different URL, using the following * mapping:
* * <servlet-mapping>
* <servlet-name>webdav</servlet-name>
* <url-pattern>/webdavedit/*</url-pattern>
* </servlet-mapping> *
*

* Don't forget to secure access appropriately to the editing URLs. With this * configuration the context will be accessible to normal users as before. Those * users with the necessary access will be able to edit content available via * http://host:port/context/content using * http://host:port/context/webdavedit/content * * @author Remy Maucherat * @version $Revision: 600268 $ $Date: 2007-12-02 12:09:55 +0100 $ */ public class WebdavServlet extends DefaultServlet { // -------------------------------------------------------------- Constants 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 infinite. */ 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:"; private static final TimeZone GMT_TIME_ZONE = TimeZone.getTimeZone("GMT"); /** * A ThreadLocal for simple date format for the creation date ISO representation (partial). */ protected static final ThreadLocal creationDateFormat = ThreadLocal.withInitial(() -> { SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); f.setTimeZone(GMT_TIME_ZONE); return f; }); /** * MD5 message digest provider. */ protected static final MessageDigest md5Helper; static { // Load the MD5 helper used to calculate signatures. try { md5Helper = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("No MD5 digest found."); } } /** * The MD5 helper object for this class. */ protected static final MD5Encoder md5Encoder = new MD5Encoder(); // ----------------------------------------------------- Instance Variables /** * Repository of the locks put on single resources. *

* Key : path
* Value : LockInfo */ private final Map resourceLocks = new ConcurrentHashMap<>(); /** * Repository of the lock-null resources. *

* Key : path of the collection containing the lock-null resource
* Value : List of lock-null resource which are members of the * collection. Each element of the List is the path associated with * the lock-null resource. */ private final Map> lockNullResources = new ConcurrentHashMap<>(); /** * List of the heritable locks. *

* Key : path
* Value : LockInfo */ private final List collectionLocks = new CopyOnWriteArrayList<>(); /** * Secret information used to generate reasonably secure lock ids. */ private String secret = "catalina"; // --------------------------------------------------------- Public Methods /** * Initialize this servlet. */ @Override public void init() throws ServletException { super.init(); if (getServletConfig().getInitParameter("secret") != null) secret = getServletConfig().getInitParameter("secret"); } // ------------------------------------------------------ Protected Methods /** * Return JAXP document builder instance. */ protected DocumentBuilder getDocumentBuilder() throws ServletException { DocumentBuilder documentBuilder = null; DocumentBuilderFactory documentBuilderFactory = null; try { documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setValidating(true); documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); documentBuilderFactory.setNamespaceAware(true); documentBuilderFactory.setExpandEntityReferences(false); documentBuilder = documentBuilderFactory.newDocumentBuilder(); documentBuilder.setEntityResolver( new WebdavResolver(this.getServletContext())); } catch(ParserConfigurationException e) { throw new ServletException (rb.getString(LogFacade.JAXP_INTI_FAILED)); } return documentBuilder; } /** * Handles the special WebDAV methods. */ @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if (debug > 0) { String path = getRelativePath(req); log("[" + method + "] " + path); } switch (method) { case METHOD_PROPFIND: doPropfind(req, resp); break; case METHOD_PROPPATCH: doProppatch(req, resp); break; case METHOD_MKCOL: doMkcol(req, resp); break; case METHOD_COPY: doCopy(req, resp); break; case METHOD_MOVE: doMove(req, resp); break; case METHOD_LOCK: doLock(req, resp); break; case METHOD_UNLOCK: doUnlock(req, resp); break; default: // DefaultServlet processing super.service(req, resp); break; } } /** * Check if the conditions specified in the optional If headers are * satisfied. * * @param request The servlet request we are processing * @param response The servlet response we are creating * @param resourceAttributes The resource information * @return boolean true if the resource meets all the specified conditions, * and false if any of the conditions is not satisfied, in which case * request processing is stopped */ @Override protected boolean checkIfHeaders(HttpServletRequest request, HttpServletResponse response, ResourceAttributes resourceAttributes) throws IOException { return super.checkIfHeaders(request, response, resourceAttributes); } /** * Override the DefaultServlet implementation and only use the PathInfo. If * the ServletPath is non-null, it will be because the WebDAV servlet has * been mapped to a url other than /* to configure editing at different url * than normal viewing. * * @param request The servlet request we are processing */ @Override protected String getRelativePath(HttpServletRequest request) { // Are we being processed by a RequestDispatcher.include()? if (request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null) { String result = (String) request.getAttribute( RequestDispatcher.INCLUDE_PATH_INFO); if (result == null || "".equals(result)) result = "/"; return result; } // No, extract the desired path directly from the request String result = request.getPathInfo(); if (result == null || "".equals(result)) { result = "/"; } return result; } /** * OPTIONS Method. * * @param req The request * @param resp The response * @throws ServletException If an error occurs * @throws IOException If an IO error occurs */ protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.addHeader("DAV", "1,2"); StringBuilder methodsAllowed = determineMethodsAllowed(resources, req); resp.addHeader("Allow", methodsAllowed.toString()); resp.addHeader("MS-Author-Via", "DAV"); } /** * PROPFIND Method. */ protected void doPropfind(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (!listings) { // Get allowed methods StringBuilder methodsAllowed = determineMethodsAllowed(resources, req); resp.addHeader("Allow", methodsAllowed.toString()); resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED); return; } String path = getRelativePath(req); if (path.endsWith("/")) path = path.substring(0, path.length() - 1); if (path.toUpperCase(Locale.ENGLISH).startsWith("/WEB-INF") || path.toUpperCase(Locale.ENGLISH).startsWith("/META-INF")) { resp.sendError(WebdavStatus.SC_FORBIDDEN); return; } // Propfind depth int depth = INFINITY; // Propfind type int type = FIND_ALL_PROP; String depthStr = req.getHeader("Depth"); if (depthStr == null) { depth = INFINITY; } else { switch (depthStr) { case "0": depth = 0; break; case "1": depth = 1; break; case "infinity": depth = INFINITY; break; default: break; } } Node propNode = null; if (req.getInputStream().available() > 0) { DocumentBuilder documentBuilder = getDocumentBuilder(); 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; default: break; } } } catch (SAXException | IOException e) { // Something went wrong - use the defaults. } } // Properties which are to be displayed. List properties = new ArrayList<>(); if (type == FIND_BY_PROPERTY && propNode != null) { 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 = nodeName.indexOf(':') != -1 ? nodeName.substring(nodeName.indexOf(':') + 1) : nodeName; // href is a live property which is handled differently properties.add(propertyName); break; default: break; } } } boolean exists = true; Object object = null; try { object = resources.lookup(path); } catch (NamingException e) { exists = false; int slash = path.lastIndexOf('/'); if (slash != -1) { String parentPath = path.substring(0, slash); List currentLockNullResources = lockNullResources.get(parentPath); if (currentLockNullResources != null) { for (String lockNullPath : currentLockNullResources) { if (lockNullPath.equals(path)) { resp.setStatus(WebdavStatus.SC_MULTI_STATUS); resp.setContentType("text/xml; charset=UTF-8"); // Create multistatus object XMLWriter generatedXML = new XMLWriter(resp.getWriter()); generatedXML.writeXMLHeader(); generatedXML.writeElement (null, "multistatus" + generateNamespaceDeclarations(), XMLWriter.OPENING); parseLockNullProperties (req, generatedXML, lockNullPath, type, properties); generatedXML.writeElement(null, "multistatus", XMLWriter.CLOSING); generatedXML.sendData(); return; } } } } } if (!exists) { resp.sendError(HttpServletResponse.SC_NOT_FOUND, path); return; } resp.setStatus(WebdavStatus.SC_MULTI_STATUS); resp.setContentType("text/xml; charset=UTF-8"); // Create multistatus object XMLWriter generatedXML = new XMLWriter(resp.getWriter()); generatedXML.writeXMLHeader(); generatedXML.writeElement(null, "multistatus" + generateNamespaceDeclarations(), XMLWriter.OPENING); if (depth == 0) { parseProperties(req, 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 = stack.pop(); parseProperties(req, generatedXML, currentPath, type, properties); try { object = resources.lookup(currentPath); } catch (NamingException e) { continue; } if (object instanceof DirContext && depth > 0) { try { NamingEnumeration enumeration = resources.list(currentPath); while (enumeration.hasMoreElements()) { NameClassPair ncPair = enumeration.nextElement(); String newPath = currentPath; if (!newPath.endsWith("/")) newPath += "/"; newPath += ncPair.getName(); stackBelow.push(newPath); } } catch (NamingException e) { resp.sendError (HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } // Displaying the lock-null resources present in that // collection String lockPath = currentPath; if (lockPath.endsWith("/")) lockPath = lockPath.substring(0, lockPath.length() - 1); List currentLockNullResources = lockNullResources.get(lockPath); if (currentLockNullResources != null) { for (String lockNullPath : currentLockNullResources) { parseLockNullProperties (req, generatedXML, lockNullPath, type, properties); } } } if (stack.isEmpty()) { depth--; stack = stackBelow; stackBelow = new Stack<>(); } generatedXML.sendData(); } } generatedXML.writeElement(null, "multistatus", XMLWriter.CLOSING); generatedXML.sendData(); } /** * 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; } resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED); } /** * 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); if (path.toUpperCase(Locale.ENGLISH).startsWith("/WEB-INF") || path.toUpperCase(Locale.ENGLISH).startsWith("/META-INF")) { resp.sendError(WebdavStatus.SC_FORBIDDEN); return; } boolean exists = true; try { resources.lookup(path); } catch (NamingException e) { exists = false; } // Can't create a collection if a resource already exists at the given // path if (exists) { // Get allowed methods StringBuilder methodsAllowed = determineMethodsAllowed(resources, req); resp.addHeader("Allow", methodsAllowed.toString()); resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED); return; } if (req.getInputStream().available() > 0) { DocumentBuilder documentBuilder = getDocumentBuilder(); try { documentBuilder.parse (new InputSource(req.getInputStream())); // TODO : Process this request body resp.sendError(WebdavStatus.SC_NOT_IMPLEMENTED); return; } catch(SAXException saxe) { // Parse error - assume invalid content resp.sendError(WebdavStatus.SC_BAD_REQUEST); return; } } boolean result = true; try { resources.createSubcontext(path); } catch (NamingException e) { result = false; } 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 req The servlet request we are processing * @param resp 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, false); } } /** * 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 ("0".equals(depthStr)) { 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 { int commaPos = lockDurationStr.indexOf(","); // If multiple timeouts, just use the first if (commaPos != -1) { lockDurationStr = lockDurationStr.substring(0,commaPos); } if (lockDurationStr.startsWith("Second-")) { lockDuration = Integer.parseInt(lockDurationStr.substring(7)); } else { if ("infinity".equalsIgnoreCase(lockDurationStr)) { lockDuration = MAX_TIMEOUT; } else { try { lockDuration = Integer.parseInt(lockDurationStr); } catch (NumberFormatException e) { lockDuration = MAX_TIMEOUT; } } } if (lockDuration == 0) { lockDuration = DEFAULT_TIMEOUT; } if (lockDuration > MAX_TIMEOUT) { lockDuration = MAX_TIMEOUT; } } lock.expiresAt = System.currentTimeMillis() + lockDuration * 1000L; int lockRequestType = LOCK_CREATION; Node lockInfoNode = null; DocumentBuilder documentBuilder = getDocumentBuilder(); try { Document document = documentBuilder.parse(new InputSource (req.getInputStream())); // Get the root element of the document Element rootElement = document.getDocumentElement(); lockInfoNode = rootElement; } catch (IOException | SAXException 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; default: 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(':') + 1); } else { lock.scope = tempScope; } break; default: 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; default: 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.setQualifiedNames(false); domWriter.print(currentNode); lock.owner += strWriter.toString(); break; default: break; } } if (lock.owner == null) { // Bad request resp.setStatus(WebdavStatus.SC_BAD_REQUEST); } } else { lock.owner = ""; } } String path = getRelativePath(req); lock.path = path; boolean exists = true; Object object = null; try { object = resources.lookup(path); } catch (NamingException e) { exists = false; } 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; byte[] digestBytes = null; synchronized(md5Helper) { digestBytes = md5Helper.digest(lockTokenStr.getBytes( Charset.defaultCharset())); } String lockToken = new String(md5Encoder.encode(digestBytes)); if ( exists && object instanceof DirContext && lock.depth == INFINITY) { // Locking a collection (and all its member resources) // Checking if a child resource of this collection is // already locked List lockPaths = new ArrayList<>(); for (LockInfo currentLock : collectionLocks) { 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.add(currentLock.path); } } Iterator> resourceLockIterator = resourceLocks.entrySet().iterator(); while (resourceLockIterator.hasNext()) { Map.Entry entry = resourceLockIterator.next(); LockInfo currentLock = entry.getValue(); if (currentLock.hasExpired()) { resourceLockIterator.remove(); continue; } if (currentLock.path.startsWith(lock.path) && (currentLock.isExclusive() || lock.isExclusive()) ) { // A child resource of this collection is locked lockPaths.add(currentLock.path); } } if (!lockPaths.isEmpty()) { // One of the child paths was locked // We generate a multistatus error report resp.setStatus(WebdavStatus.SC_CONFLICT); XMLWriter generatedXML = new XMLWriter(); generatedXML.writeXMLHeader(); generatedXML.writeElement (null, "multistatus" + generateNamespaceDeclarations(), XMLWriter.OPENING); for (String lockPath : lockPaths) { generatedXML.writeElement(null, "response", XMLWriter.OPENING); generatedXML.writeElement(null, "href", XMLWriter.OPENING); generatedXML .writeText(lockPath); 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 for (LockInfo currentLock : collectionLocks) { 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.add(lockToken); lock = currentLock; addLock = false; } } if (addLock) { lock.tokens.add(lockToken); collectionLocks.add(lock); } } else { // Locking a single resource // Retrieving an already existing lock on that resource LockInfo presentLock = 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.add(lockToken); lock = presentLock; } } else { lock.tokens.add(lockToken); resourceLocks.put(lock.path, lock); // Checking if a resource exists at this path exists = true; try { object = resources.lookup(path); } catch (NamingException e) { exists = false; } if (!exists) { // "Creating" a lock-null resource int slash = lock.path.lastIndexOf('/'); String parentPath = lock.path.substring(0, slash); List lockNulls = lockNullResources.get(parentPath); if (lockNulls == null) { lockNulls = new CopyOnWriteArrayList<>(); lockNullResources.put(parentPath, lockNulls); } lockNulls.add(lock.path); } // Add the Lock-Token header as by RFC 2518 8.10.1 // - only do this for newly created locks resp.addHeader("Lock-Token", ""); } } } if (lockRequestType == LOCK_REFRESH) { String ifHeader = req.getHeader("If"); if (ifHeader == null) ifHeader = ""; // Checking resource locks LockInfo toRenew = resourceLocks.get(path); if (lock != null) { // At least one of the tokens of the locks must have been given for (String token : toRenew.tokens) { if (ifHeader.contains(token)) { toRenew.expiresAt = lock.expiresAt; lock = toRenew; } } } // Checking inheritable collection locks for (LockInfo collectionLock : collectionLocks) { toRenew = collectionLock; if (path.equals(toRenew.path)) { for (String token : toRenew.tokens) { if (ifHeader.contains(token)) { 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); generatedXML.writeElement(null, "lockdiscovery", XMLWriter.CLOSING); generatedXML.writeElement(null, "prop", XMLWriter.CLOSING); resp.setStatus(WebdavStatus.SC_OK); resp.setContentType("text/xml; charset=UTF-8"); try (Writer writer = resp.getWriter()) { writer.write(generatedXML.toString()); } } /** * 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 = resourceLocks.get(path); if (lock != null) { // At least one of the tokens of the locks must have been given Iterator tokenList = lock.tokens.iterator(); while (tokenList.hasNext()) { String token = tokenList.next(); if (lockTokenHeader.contains(token)) { tokenList.remove(); } } if (lock.tokens.isEmpty()) { resourceLocks.remove(path); // Removing any lock-null resource which would be present lockNullResources.remove(path); } } // Checking inheritable collection locks for (LockInfo collectionLock : collectionLocks) { lock = collectionLock; if (path.equals(lock.path)) { Iterator tokenList = lock.tokens.iterator(); while (tokenList.hasNext()) { String token = tokenList.next(); if (lockTokenHeader.contains(token)) { tokenList.remove(); break; } } if (lock.tokens.isEmpty()) { collectionLocks.remove(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 resourceLock = resourceLocks.get(path); if (resourceLock != null) { if (resourceLock.hasExpired()) { resourceLocks.remove(path); } else { // At least one of the tokens of the locks must have been given boolean tokenMatch = resourceLock.tokens.stream().anyMatch(ifHeader::contains); if (!tokenMatch) return true; } } // Checking inheritable collection locks for (LockInfo collectionLock : collectionLocks) { if (collectionLock.hasExpired()) { collectionLocks.remove(collectionLock); } else if (path.startsWith(collectionLock.path)) { boolean tokenMatch = collectionLock.tokens.stream().anyMatch(ifHeader::contains); 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 == null) { resp.sendError(WebdavStatus.SC_BAD_REQUEST); return false; } // Remove url encoding from destination destinationPath = RequestUtil.urlDecode(destinationPath, "UTF8"); int protocolIndex = destinationPath.indexOf("://"); if (protocolIndex >= 0) { // if the Destination URL contains the protocol, we can safely // trim everything upto the first "/" character after "://" int firstSeparator = destinationPath.indexOf("/", protocolIndex + 4); if (firstSeparator < 0) { destinationPath = "/"; } else { destinationPath = destinationPath.substring(firstSeparator); } } else { String hostName = req.getServerName(); if (hostName != null && destinationPath.startsWith(hostName)) { destinationPath = destinationPath.substring(hostName.length()); } int portIndex = destinationPath.indexOf(':'); if (portIndex >= 0) { destinationPath = destinationPath.substring(portIndex); } if (destinationPath.startsWith(":")) { int firstSeparator = destinationPath.indexOf('/'); if (firstSeparator < 0) { destinationPath = "/"; } else { destinationPath = destinationPath.substring(firstSeparator); } } } // Normalise destination path (remove '.' and '..') destinationPath = RequestUtil.normalize(destinationPath); 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) log("Dest path :" + destinationPath); if (destinationPath.toUpperCase(Locale.ENGLISH).startsWith("/WEB-INF") || destinationPath.toUpperCase(Locale.ENGLISH).startsWith("/META-INF")) { resp.sendError(WebdavStatus.SC_FORBIDDEN); return false; } String path = getRelativePath(req); if (path.toUpperCase(Locale.ENGLISH).startsWith("/WEB-INF") || path.toUpperCase(Locale.ENGLISH).startsWith("/META-INF")) { resp.sendError(WebdavStatus.SC_FORBIDDEN); return false; } 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 ("T".equalsIgnoreCase(overwriteHeader)) { overwrite = true; } else { overwrite = false; } } // Overwriting the destination boolean exists = true; try { resources.lookup(destinationPath); } catch (NamingException e) { exists = false; } if (overwrite) { // Delete destination resource, if it exists if (exists) { if (!deleteResource(destinationPath, req, resp, true)) { return false; } } else { resp.setStatus(WebdavStatus.SC_CREATED); } } else { // If the destination exists, then it's a conflict if (exists) { 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; } // Copy was successful resp.setStatus(WebdavStatus.SC_CREATED); // 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 occurred * during the copy operation * @param source Path of the resource to be copied * @param dest Destination path */ private boolean copyResource(DirContext resources, Hashtable errorList, String source, String dest) { if (debug > 1) log("Copy: " + source + " To: " + dest); Object object = null; try { object = resources.lookup(source); } catch (NamingException e) { // Ignore } if (object instanceof DirContext) { try { resources.createSubcontext(dest); } catch (NamingException e) { errorList.put(dest, WebdavStatus.SC_CONFLICT); return false; } try { NamingEnumeration enumeration = resources.list(source); while (enumeration.hasMoreElements()) { NameClassPair ncPair = enumeration.nextElement(); String childDest = dest; if (!"/".equals(childDest)) childDest += "/"; childDest += ncPair.getName(); String childSrc = source; if (!"/".equals(childSrc)) childSrc += "/"; childSrc += ncPair.getName(); copyResource(resources, errorList, childSrc, childDest); } } catch (NamingException e) { errorList.put(dest, WebdavStatus.SC_INTERNAL_SERVER_ERROR); return false; } } else { if (object instanceof Resource) { try { resources.bind(dest, object); } catch (NamingException e) { errorList.put(source, WebdavStatus.SC_INTERNAL_SERVER_ERROR); return false; } } else { errorList.put(source, 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, true); } /** * Delete a resource. * * @param path Path of the resource which is to be deleted * @param req Servlet request * @param resp Servlet response * @param setStatus Should the response status be set on successful * completion */ private boolean deleteResource(String path, HttpServletRequest req, HttpServletResponse resp, boolean setStatus) throws ServletException, IOException { if (path.toUpperCase(Locale.ENGLISH).startsWith("/WEB-INF") || path.toUpperCase(Locale.ENGLISH).startsWith("/META-INF")) { resp.sendError(WebdavStatus.SC_FORBIDDEN); return false; } 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; } boolean exists = true; Object object = null; try { object = resources.lookup(path); } catch (NamingException e) { exists = false; } if (!exists) { resp.sendError(WebdavStatus.SC_NOT_FOUND); return false; } boolean collection = object instanceof DirContext; if (!collection) { try { resources.unbind(path); } catch (NamingException e) { resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); return false; } } else { Hashtable errorList = new Hashtable(); deleteCollection(req, resources, path, errorList); try { resources.unbind(path); } catch (NamingException e) { errorList.put(path, WebdavStatus.SC_INTERNAL_SERVER_ERROR); } if (!errorList.isEmpty()) { sendReport(req, resp, errorList); return false; } } if (setStatus) { 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 occurred */ private void deleteCollection(HttpServletRequest req, DirContext resources, String path, Hashtable errorList) { if (debug > 1) log("Delete:" + path); if (path.toUpperCase(Locale.ENGLISH).startsWith("/WEB-INF") || path.toUpperCase(Locale.ENGLISH).startsWith("/META-INF")) { errorList.put(path, WebdavStatus.SC_FORBIDDEN); return; } String ifHeader = req.getHeader("If"); if (ifHeader == null) ifHeader = ""; String lockTokenHeader = req.getHeader("Lock-Token"); if (lockTokenHeader == null) lockTokenHeader = ""; Enumeration enumeration; try { enumeration = resources.list(path); } catch (NamingException e) { errorList.put(path, WebdavStatus.SC_INTERNAL_SERVER_ERROR); return; } while (enumeration.hasMoreElements()) { NameClassPair ncPair = enumeration.nextElement(); String childName = path; if (!"/".equals(childName)) childName += "/"; childName += ncPair.getName(); if (isLocked(childName, ifHeader + lockTokenHeader)) { errorList.put(childName, WebdavStatus.SC_LOCKED); } else { try { Object object = resources.lookup(childName); if (object instanceof DirContext) { deleteCollection(req, resources, childName, errorList); } try { resources.unbind(childName); } catch (NamingException e) { if (!(object instanceof DirContext)) { // If it's not a collection, then it's an unknown // error errorList.put(childName, WebdavStatus.SC_INTERNAL_SERVER_ERROR); } } } catch (NamingException e) { errorList.put(childName, 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 = pathList.nextElement(); int errorCode = errorList.get(errorPath); 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); try (Writer writer = resp.getWriter()) { writer.write(generatedXML.toString()); } } /** * Propfind helper method. * * @param req The servlet request * @param generatedXML XML response to the Propfind request * @param path Path of the current resource * @param type Propfind type * @param propertiesList If the propfind type is find properties by * name, then this List contains those properties */ private void parseProperties(HttpServletRequest req, XMLWriter generatedXML, String path, int type, List propertiesList) { // Exclude any resource in the /WEB-INF and /META-INF subdirectories // (the "toUpperCase()" avoids problems on Windows systems) if (path.toUpperCase(Locale.ENGLISH).startsWith("/WEB-INF") || path.toUpperCase(Locale.ENGLISH).startsWith("/META-INF")) return; CacheEntry cacheEntry = resources.lookupCache(path); generatedXML.writeElement(null, "response", XMLWriter.OPENING); String status = "HTTP/1.1 " + WebdavStatus.SC_OK + " " + WebdavStatus.getStatusText(WebdavStatus.SC_OK); // Generating href element generatedXML.writeElement(null, "href", XMLWriter.OPENING); String href = req.getContextPath() + req.getServletPath(); if (href.endsWith("/") && path.startsWith("/")) href += path.substring(1); else href += path; if (cacheEntry.context != null && !href.endsWith("/")) href += "/"; generatedXML.writeText(rewriteUrl(href)); generatedXML.writeElement(null, "href", XMLWriter.CLOSING); String resourceName = path; int lastSlash = path.lastIndexOf('/'); if (lastSlash != -1) resourceName = resourceName.substring(lastSlash + 1); switch (type) { case FIND_ALL_PROP : generatedXML.writeElement(null, "propstat", XMLWriter.OPENING); generatedXML.writeElement(null, "prop", XMLWriter.OPENING); generatedXML.writeProperty (null, "creationdate", getISOCreationDate(cacheEntry.attributes.getCreation())); generatedXML.writeElement(null, "displayname", XMLWriter.OPENING); generatedXML.writeData(resourceName); generatedXML.writeElement(null, "displayname", XMLWriter.CLOSING); if (cacheEntry.resource != null) { generatedXML.writeProperty (null, "getlastmodified", FastHttpDateFormat.formatDate (cacheEntry.attributes.getLastModified(), null)); generatedXML.writeProperty (null, "getcontentlength", String.valueOf(cacheEntry.attributes.getContentLength())); String contentType = getServletContext().getMimeType (cacheEntry.name); if (contentType != null) { generatedXML.writeProperty(null, "getcontenttype", contentType); } generatedXML.writeProperty(null, "getetag", cacheEntry.attributes.getETag()); 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 = "" + "" + "" + "" + "" + "" + "" + ""; generatedXML.writeElement(null, "supportedlock", XMLWriter.OPENING); generatedXML.writeText(supportedLocks); generatedXML.writeElement(null, "supportedlock", XMLWriter.CLOSING); generateLockDiscovery(path, generatedXML); generatedXML.writeElement(null, "prop", XMLWriter.CLOSING); generatedXML.writeElement(null, "status", XMLWriter.OPENING); generatedXML.writeText(status); generatedXML.writeElement(null, "status", XMLWriter.CLOSING); 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 (cacheEntry.resource != null) { 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.writeElement(null, "status", XMLWriter.OPENING); generatedXML.writeText(status); generatedXML.writeElement(null, "status", XMLWriter.CLOSING); generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING); break; case FIND_BY_PROPERTY : List propertiesNotFound = new ArrayList<>(); // Parse the list of properties generatedXML.writeElement(null, "propstat", XMLWriter.OPENING); generatedXML.writeElement(null, "prop", XMLWriter.OPENING); for (String property : propertiesList) { if (null == property) { propertiesNotFound.add(property); } else switch (property) { case "creationdate": generatedXML.writeProperty (null, "creationdate", getISOCreationDate(cacheEntry.attributes.getCreation())); break; case "displayname": generatedXML.writeElement (null, "displayname", XMLWriter.OPENING); generatedXML.writeData(resourceName); generatedXML.writeElement (null, "displayname", XMLWriter.CLOSING); break; case "getcontentlanguage": if (cacheEntry.context != null) { propertiesNotFound.add(property); } else { generatedXML.writeElement(null, "getcontentlanguage", XMLWriter.NO_CONTENT); } break; case "getcontentlength": if (cacheEntry.context != null) { propertiesNotFound.add(property); } else { generatedXML.writeProperty (null, "getcontentlength", String.valueOf(cacheEntry.attributes.getContentLength())); } break; case "getcontenttype": if (cacheEntry.context != null) { propertiesNotFound.add(property); } else { generatedXML.writeProperty (null, "getcontenttype", getServletContext().getMimeType (cacheEntry.name)); } break; case "getetag": if (cacheEntry.context != null) { propertiesNotFound.add(property); } else { generatedXML.writeProperty (null, "getetag", cacheEntry.attributes.getETag()); } break; case "getlastmodified": if (cacheEntry.context != null) { propertiesNotFound.add(property); } else { generatedXML.writeProperty (null, "getlastmodified", FastHttpDateFormat.formatDate (cacheEntry.attributes.getLastModified(), null)); } break; case "resourcetype": if (cacheEntry.context != null) { 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); } break; case "source": generatedXML.writeProperty(null, "source", ""); break; case "supportedlock": supportedLocks = "" + "" + "" + "" + "" + "" + "" + ""; generatedXML.writeElement(null, "supportedlock", XMLWriter.OPENING); generatedXML.writeText(supportedLocks); generatedXML.writeElement(null, "supportedlock", XMLWriter.CLOSING); break; case "lockdiscovery": if (!generateLockDiscovery(path, generatedXML)) propertiesNotFound.add(property); break; default: propertiesNotFound.add(property); break; } } generatedXML.writeElement(null, "prop", XMLWriter.CLOSING); generatedXML.writeElement(null, "status", XMLWriter.OPENING); generatedXML.writeText(status); generatedXML.writeElement(null, "status", XMLWriter.CLOSING); generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING); Iterator propertiesNotFoundList = propertiesNotFound.iterator(); if (propertiesNotFoundList.hasNext()) { status = "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.hasNext()) { generatedXML.writeElement (null, propertiesNotFoundList.next(), XMLWriter.NO_CONTENT); } generatedXML.writeElement(null, "prop", XMLWriter.CLOSING); generatedXML.writeElement(null, "status", XMLWriter.OPENING); generatedXML.writeText(status); generatedXML.writeElement(null, "status", XMLWriter.CLOSING); generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING); } break; default: // not possible break; } generatedXML.writeElement(null, "response", XMLWriter.CLOSING); } /** * Propfind helper method. Displays the properties of a lock-null resource. * * @param generatedXML XML response to the Propfind request * @param path Path of the current resource * @param type Propfind type * @param propertiesList If the propfind type is find properties by * name, then this List contains those properties */ private void parseLockNullProperties(HttpServletRequest req, XMLWriter generatedXML, String path, int type, List propertiesList) { // Exclude any resource in the /WEB-INF and /META-INF subdirectories // (the "toUpperCase()" avoids problems on Windows systems) if (path.toUpperCase(Locale.ENGLISH).startsWith("/WEB-INF") || path.toUpperCase(Locale.ENGLISH).startsWith("/META-INF")) return; // Retrieving the lock associated with the lock-null resource LockInfo lock = resourceLocks.get(path); if (lock == null) return; generatedXML.writeElement(null, "response", XMLWriter.OPENING); String status = "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(rewriteUrl(RequestUtil.normalize( absoluteUri + toAppend))); generatedXML.writeElement(null, "href", XMLWriter.CLOSING); String resourceName = path; int lastSlash = path.lastIndexOf('/'); if (lastSlash != -1) resourceName = resourceName.substring(lastSlash + 1); switch (type) { case FIND_ALL_PROP : generatedXML.writeElement(null, "propstat", XMLWriter.OPENING); generatedXML.writeElement(null, "prop", XMLWriter.OPENING); generatedXML.writeProperty (null, "creationdate", getISOCreationDate(lock.creationDate.getTime())); generatedXML.writeElement (null, "displayname", XMLWriter.OPENING); generatedXML.writeData(resourceName); generatedXML.writeElement (null, "displayname", XMLWriter.CLOSING); generatedXML.writeProperty(null, "getlastmodified", FastHttpDateFormat.formatDate (lock.creationDate.getTime(), null)); 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 = "" + "" + "" + "" + "" + "" + "" + ""; generatedXML.writeElement(null, "supportedlock", XMLWriter.OPENING); generatedXML.writeText(supportedLocks); generatedXML.writeElement(null, "supportedlock", XMLWriter.CLOSING); generateLockDiscovery(path, generatedXML); generatedXML.writeElement(null, "prop", XMLWriter.CLOSING); generatedXML.writeElement(null, "status", XMLWriter.OPENING); generatedXML.writeText(status); generatedXML.writeElement(null, "status", XMLWriter.CLOSING); 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.writeElement(null, "status", XMLWriter.OPENING); generatedXML.writeText(status); generatedXML.writeElement(null, "status", XMLWriter.CLOSING); generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING); break; case FIND_BY_PROPERTY : List propertiesNotFound = new ArrayList<>(); // Parse the list of properties generatedXML.writeElement(null, "propstat", XMLWriter.OPENING); generatedXML.writeElement(null, "prop", XMLWriter.OPENING); for (String property : propertiesList) { if (null == property) { propertiesNotFound.add(property); } else switch (property) { case "creationdate": generatedXML.writeProperty (null, "creationdate", getISOCreationDate(lock.creationDate.getTime())); break; case "displayname": generatedXML.writeElement (null, "displayname", XMLWriter.OPENING); generatedXML.writeData(resourceName); generatedXML.writeElement (null, "displayname", XMLWriter.CLOSING); break; case "getcontentlanguage": generatedXML.writeElement(null, "getcontentlanguage", XMLWriter.NO_CONTENT); break; case "getcontentlength": generatedXML.writeProperty (null, "getcontentlength", String.valueOf(0)); break; case "getcontenttype": generatedXML.writeProperty (null, "getcontenttype", ""); break; case "getetag": generatedXML.writeProperty(null, "getetag", ""); break; case "getlastmodified": generatedXML.writeProperty (null, "getlastmodified", FastHttpDateFormat.formatDate (lock.creationDate.getTime(), null)); break; case "resourcetype": generatedXML.writeElement(null, "resourcetype", XMLWriter.OPENING); generatedXML.writeElement(null, "lock-null", XMLWriter.NO_CONTENT); generatedXML.writeElement(null, "resourcetype", XMLWriter.CLOSING); break; case "source": generatedXML.writeProperty(null, "source", ""); break; case "supportedlock": supportedLocks = "" + "" + "" + "" + "" + "" + "" + ""; generatedXML.writeElement(null, "supportedlock", XMLWriter.OPENING); generatedXML.writeText(supportedLocks); generatedXML.writeElement(null, "supportedlock", XMLWriter.CLOSING); break; case "lockdiscovery": if (!generateLockDiscovery(path, generatedXML)) propertiesNotFound.add(property); break; default: propertiesNotFound.add(property); break; } } generatedXML.writeElement(null, "prop", XMLWriter.CLOSING); generatedXML.writeElement(null, "status", XMLWriter.OPENING); generatedXML.writeText(status); generatedXML.writeElement(null, "status", XMLWriter.CLOSING); generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING); Iterator propertiesNotFoundList = propertiesNotFound.iterator(); if (propertiesNotFoundList.hasNext()) { status = "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.hasNext()) { generatedXML.writeElement (null, propertiesNotFoundList.next(), XMLWriter.NO_CONTENT); } generatedXML.writeElement(null, "prop", XMLWriter.CLOSING); generatedXML.writeElement(null, "status", XMLWriter.OPENING); generatedXML.writeText(status); generatedXML.writeElement(null, "status", XMLWriter.CLOSING); generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING); } break; default: // not possible 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 * @return true if at least one lock was displayed */ private boolean generateLockDiscovery (String path, XMLWriter generatedXML) { LockInfo resourceLock = resourceLocks.get(path); boolean wroteStart = false; if (resourceLock != null) { wroteStart = true; generatedXML.writeElement(null, "lockdiscovery", XMLWriter.OPENING); resourceLock.toXML(generatedXML); } for (LockInfo currentLock : collectionLocks) { if (path.startsWith(currentLock.path)) { if (!wroteStart) { wroteStart = true; generatedXML.writeElement(null, "lockdiscovery", XMLWriter.OPENING); } currentLock.toXML(generatedXML); } } if (wroteStart) { generatedXML.writeElement(null, "lockdiscovery", XMLWriter.CLOSING); } else { return false; } return true; } /** * Get creation date in ISO format. */ private String getISOCreationDate(long creationDate) { StringBuilder creationDateValue = new StringBuilder (creationDateFormat.get().format (new Date(creationDate))); /* int offset = Calendar.getInstance().getTimeZone().getRawOffset() / 3600000; // FIXME ? if (offset < 0) { creationDateValue.append("-"); offset = -offset; } else if (offset > 0) { creationDateValue.append("+"); } if (offset != 0) { if (offset < 10) creationDateValue.append("0"); creationDateValue.append(offset + ":00"); } else { creationDateValue.append("Z"); } */ return creationDateValue.toString(); } /** * Determines the methods normally allowed for the resource. * */ private StringBuilder determineMethodsAllowed(DirContext resources, HttpServletRequest req) { StringBuilder methodsAllowed = new StringBuilder(); boolean exists = true; Object object = null; try { String path = getRelativePath(req); object = resources.lookup(path); } catch (NamingException e) { exists = false; } if (!exists) { methodsAllowed.append("OPTIONS, MKCOL, PUT, LOCK"); return methodsAllowed; } methodsAllowed.append("OPTIONS, GET, HEAD, POST, DELETE, TRACE"); methodsAllowed.append(", PROPPATCH, COPY, MOVE, LOCK, UNLOCK"); if (listings) { methodsAllowed.append(", PROPFIND"); } if (!(object instanceof DirContext)) { methodsAllowed.append(", PUT"); } return methodsAllowed; } // -------------------------------------------------- LockInfo Inner Class /** * Holds a lock information. */ private static class LockInfo { // -------------------------------------------------------- Constructor /** * Constructor. */ public LockInfo() { // Ignore } // ------------------------------------------------- Instance Variables String path = "/"; String type = "write"; String scope = "exclusive"; int depth = 0; String owner = ""; List tokens = new ArrayList<>(); long expiresAt = 0; Date creationDate = new Date(); // ----------------------------------------------------- Public Methods /** * Get a String representation of this lock token. */ @Override public String toString() { StringBuilder result = new StringBuilder("Type:"); result.append(type).append("\n"); result.append("Scope:").append(scope).append("\n"); result.append("Depth:").append(depth).append("\n"); result.append("Owner:").append(owner).append("\n"); result.append("Expiration:").append( FastHttpDateFormat.formatDate(expiresAt, null)).append("\n"); for (String token : tokens) { result.append("Token:").append(token).append("\n"); } return result.toString(); } /** * 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 "exclusive".equals(scope); } /** * 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) { 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); for (String token : tokens) { generatedXML.writeElement(null, "href", XMLWriter.OPENING); generatedXML.writeText("opaquelocktoken:" + token); generatedXML.writeElement(null, "href", XMLWriter.CLOSING); } generatedXML.writeElement(null, "locktoken", XMLWriter.CLOSING); generatedXML.writeElement(null, "activelock", XMLWriter.CLOSING); } } // --------------------------------------------- WebdavResolver Inner Class /** * Work around for XML parsers that don't fully respect * {@link DocumentBuilderFactory#setExpandEntityReferences(boolean)}. External * references are filtered out for security reasons. See CVE-2007-5461. */ private static class WebdavResolver implements EntityResolver { private final ServletContext context; public WebdavResolver(ServletContext theContext) { context = theContext; } @Override public InputSource resolveEntity (String publicId, String systemId) { String msg = MessageFormat.format(rb.getString(LogFacade.IGNORED_EXTERNAL_ENTITY_INFO), new Object[] {publicId, systemId}); context.log(msg); return new InputSource( new StringReader("Ignored external entity")); } } } // -------------------------------------------------------- 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 final 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 collides with HTTP 1.1 // "207 Partial 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 collides 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 collides 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 status 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 = nHttpStatusCode; if (!mapStatusCodes.containsKey(intKey)) { return ""; } else { return 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(nKey, strVal); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy