org.apache.catalina.servlets.WebdavServlet Maven / Gradle / Ivy
Show all versions of payara-micro Show documentation
/*
* 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);
}
}