2835 lines
97 KiB
Java
2835 lines
97 KiB
Java
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership.
|
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
* (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package org.apache.catalina.servlets;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.Serializable;
|
|
import java.io.StringReader;
|
|
import java.io.StringWriter;
|
|
import java.io.Writer;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.Date;
|
|
import java.util.Enumeration;
|
|
import java.util.Hashtable;
|
|
import java.util.Locale;
|
|
import java.util.Stack;
|
|
import java.util.TimeZone;
|
|
import java.util.Vector;
|
|
|
|
import javax.servlet.DispatcherType;
|
|
import javax.servlet.RequestDispatcher;
|
|
import javax.servlet.ServletContext;
|
|
import javax.servlet.ServletException;
|
|
import javax.servlet.http.HttpServletRequest;
|
|
import javax.servlet.http.HttpServletResponse;
|
|
import javax.xml.parsers.DocumentBuilder;
|
|
import javax.xml.parsers.DocumentBuilderFactory;
|
|
import javax.xml.parsers.ParserConfigurationException;
|
|
|
|
import org.apache.catalina.WebResource;
|
|
import org.apache.catalina.connector.RequestFacade;
|
|
import org.apache.catalina.util.DOMWriter;
|
|
import org.apache.catalina.util.URLEncoder;
|
|
import org.apache.catalina.util.XMLWriter;
|
|
import org.apache.tomcat.util.buf.UDecoder;
|
|
import org.apache.tomcat.util.http.ConcurrentDateFormat;
|
|
import org.apache.tomcat.util.http.FastHttpDateFormat;
|
|
import org.apache.tomcat.util.http.RequestUtil;
|
|
import org.apache.tomcat.util.security.ConcurrentMessageDigest;
|
|
import org.apache.tomcat.util.security.MD5Encoder;
|
|
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;
|
|
|
|
/**
|
|
* Servlet which adds support for
|
|
* <a href="https://tools.ietf.org/html/rfc4918">WebDAV</a>
|
|
* <a href="https://tools.ietf.org/html/rfc4918#section-18">level 2</a>.
|
|
* 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.
|
|
* <p>
|
|
* Mapping a subpath (e.g. <code>/webdav/*</code> to this servlet has the effect
|
|
* of re-mounting the entire web application under that sub-path, with WebDAV
|
|
* access to all the resources. The <code>WEB-INF</code> and <code>META-INF</code>
|
|
* directories are protected in this re-mounted resource tree.
|
|
* <p>
|
|
* To enable WebDAV for a context add the following to web.xml:
|
|
* <pre>
|
|
* <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>false</param-value>
|
|
* </init-param>
|
|
* </servlet>
|
|
* <servlet-mapping>
|
|
* <servlet-name>webdav</servlet-name>
|
|
* <url-pattern>/*</url-pattern>
|
|
* </servlet-mapping>
|
|
* </pre>
|
|
* This will enable read only access. To enable read-write access add:
|
|
* <pre>
|
|
* <init-param>
|
|
* <param-name>readonly</param-name>
|
|
* <param-value>false</param-value>
|
|
* </init-param>
|
|
* </pre>
|
|
* To make the content editable via a different URL, use the following
|
|
* mapping:
|
|
* <pre>
|
|
* <servlet-mapping>
|
|
* <servlet-name>webdav</servlet-name>
|
|
* <url-pattern>/webdavedit/*</url-pattern>
|
|
* </servlet-mapping>
|
|
* </pre>
|
|
* By default access to /WEB-INF and META-INF are not available via WebDAV. To
|
|
* enable access to these URLs, use add:
|
|
* <pre>
|
|
* <init-param>
|
|
* <param-name>allowSpecialPaths</param-name>
|
|
* <param-value>true</param-value>
|
|
* </init-param>
|
|
* </pre>
|
|
* Don't forget to secure access appropriately to the editing URLs, especially
|
|
* if allowSpecialPaths is used. With the mapping configuration above, 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
|
|
*
|
|
* @see <a href="https://tools.ietf.org/html/rfc4918">RFC 4918</a>
|
|
*/
|
|
public class WebdavServlet extends DefaultServlet {
|
|
|
|
private static final long serialVersionUID = 1L;
|
|
|
|
|
|
// -------------------------------------------------------------- Constants
|
|
|
|
private static final URLEncoder URL_ENCODER_XML;
|
|
static {
|
|
URL_ENCODER_XML = (URLEncoder) URLEncoder.DEFAULT.clone();
|
|
// Remove '&' from the safe character set since while it it permitted
|
|
// in a URI path, it is not permitted in XML and encoding it is a simple
|
|
// way to address this.
|
|
URL_ENCODER_XML.removeSafeCharacter('&');
|
|
}
|
|
|
|
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";
|
|
|
|
|
|
/**
|
|
* 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:";
|
|
|
|
|
|
/**
|
|
* Simple date format for the creation date ISO representation (partial).
|
|
*/
|
|
protected static final ConcurrentDateFormat creationDateFormat =
|
|
new ConcurrentDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US,
|
|
TimeZone.getTimeZone("GMT"));
|
|
|
|
|
|
// ----------------------------------------------------- Instance Variables
|
|
|
|
/**
|
|
* Repository of the locks put on single resources.
|
|
* <p>
|
|
* Key : path <br>
|
|
* Value : LockInfo
|
|
*/
|
|
private final Hashtable<String,LockInfo> resourceLocks = new Hashtable<>();
|
|
|
|
|
|
/**
|
|
* Repository of the lock-null resources.
|
|
* <p>
|
|
* Key : path of the collection containing the lock-null resource<br>
|
|
* Value : Vector of lock-null resource which are members of the
|
|
* collection. Each element of the Vector is the path associated with
|
|
* the lock-null resource.
|
|
*/
|
|
private final Hashtable<String,Vector<String>> lockNullResources =
|
|
new Hashtable<>();
|
|
|
|
|
|
/**
|
|
* Vector of the heritable locks.
|
|
* <p>
|
|
* Key : path <br>
|
|
* Value : LockInfo
|
|
*/
|
|
private final Vector<LockInfo> collectionLocks = new Vector<>();
|
|
|
|
|
|
/**
|
|
* Secret information used to generate reasonably secure lock ids.
|
|
*/
|
|
private String secret = "catalina";
|
|
|
|
|
|
/**
|
|
* Default depth in spec is infinite. Limit depth to 3 by default as
|
|
* infinite depth makes operations very expensive.
|
|
*/
|
|
private int maxDepth = 3;
|
|
|
|
|
|
/**
|
|
* Is access allowed via WebDAV to the special paths (/WEB-INF and
|
|
* /META-INF)?
|
|
*/
|
|
private boolean allowSpecialPaths = false;
|
|
|
|
|
|
// --------------------------------------------------------- Public Methods
|
|
|
|
|
|
/**
|
|
* Initialize this servlet.
|
|
*/
|
|
@Override
|
|
public void init()
|
|
throws ServletException {
|
|
|
|
super.init();
|
|
|
|
if (getServletConfig().getInitParameter("secret") != null)
|
|
secret = getServletConfig().getInitParameter("secret");
|
|
|
|
if (getServletConfig().getInitParameter("maxDepth") != null)
|
|
maxDepth = Integer.parseInt(
|
|
getServletConfig().getInitParameter("maxDepth"));
|
|
|
|
if (getServletConfig().getInitParameter("allowSpecialPaths") != null)
|
|
allowSpecialPaths = Boolean.parseBoolean(
|
|
getServletConfig().getInitParameter("allowSpecialPaths"));
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------ Protected Methods
|
|
|
|
|
|
/**
|
|
* Return JAXP document builder instance.
|
|
* @return the document builder
|
|
* @throws ServletException document builder creation failed
|
|
* (wrapped <code>ParserConfigurationException</code> exception)
|
|
*/
|
|
protected DocumentBuilder getDocumentBuilder()
|
|
throws ServletException {
|
|
DocumentBuilder documentBuilder = null;
|
|
DocumentBuilderFactory documentBuilderFactory = null;
|
|
try {
|
|
documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
|
documentBuilderFactory.setNamespaceAware(true);
|
|
documentBuilderFactory.setExpandEntityReferences(false);
|
|
documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
|
documentBuilder.setEntityResolver(
|
|
new WebdavResolver(this.getServletContext()));
|
|
} catch(ParserConfigurationException e) {
|
|
throw new ServletException
|
|
(sm.getString("webdavservlet.jaxpfailed"));
|
|
}
|
|
return documentBuilder;
|
|
}
|
|
|
|
|
|
/**
|
|
* Handles the special WebDAV methods.
|
|
*/
|
|
@Override
|
|
protected void service(HttpServletRequest req, HttpServletResponse resp)
|
|
throws ServletException, IOException {
|
|
|
|
final String path = getRelativePath(req);
|
|
|
|
// Error page check needs to come before special path check since
|
|
// custom error pages are often located below WEB-INF so they are
|
|
// not directly accessible.
|
|
if (req.getDispatcherType() == DispatcherType.ERROR) {
|
|
doGet(req, resp);
|
|
return;
|
|
}
|
|
|
|
// Block access to special subdirectories.
|
|
// DefaultServlet assumes it services resources from the root of the web app
|
|
// and doesn't add any special path protection
|
|
// WebdavServlet remounts the webapp under a new path, so this check is
|
|
// necessary on all methods (including GET).
|
|
if (isSpecialPath(path)) {
|
|
resp.sendError(WebdavStatus.SC_NOT_FOUND);
|
|
return;
|
|
}
|
|
|
|
final String method = req.getMethod();
|
|
|
|
if (debug > 0) {
|
|
log("[" + method + "] " + path);
|
|
}
|
|
|
|
if (method.equals(METHOD_PROPFIND)) {
|
|
doPropfind(req, resp);
|
|
} else if (method.equals(METHOD_PROPPATCH)) {
|
|
doProppatch(req, resp);
|
|
} else if (method.equals(METHOD_MKCOL)) {
|
|
doMkcol(req, resp);
|
|
} else if (method.equals(METHOD_COPY)) {
|
|
doCopy(req, resp);
|
|
} else if (method.equals(METHOD_MOVE)) {
|
|
doMove(req, resp);
|
|
} else if (method.equals(METHOD_LOCK)) {
|
|
doLock(req, resp);
|
|
} else if (method.equals(METHOD_UNLOCK)) {
|
|
doUnlock(req, resp);
|
|
} else {
|
|
// DefaultServlet processing
|
|
super.service(req, resp);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* Checks whether a given path refers to a resource under
|
|
* <code>WEB-INF</code> or <code>META-INF</code>.
|
|
* @param path the full path of the resource being accessed
|
|
* @return <code>true</code> if the resource specified is under a special path
|
|
*/
|
|
private final boolean isSpecialPath(final String path) {
|
|
return !allowSpecialPaths && (
|
|
path.toUpperCase(Locale.ENGLISH).startsWith("/WEB-INF") ||
|
|
path.toUpperCase(Locale.ENGLISH).startsWith("/META-INF"));
|
|
}
|
|
|
|
|
|
@Override
|
|
protected boolean checkIfHeaders(HttpServletRequest request,
|
|
HttpServletResponse response,
|
|
WebResource resource)
|
|
throws IOException {
|
|
|
|
if (!super.checkIfHeaders(request, response, resource))
|
|
return false;
|
|
|
|
// TODO : Checking the WebDAV If header
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* URL rewriter.
|
|
*
|
|
* @param path Path which has to be rewritten
|
|
* @return the rewritten path
|
|
*/
|
|
@Override
|
|
protected String rewriteUrl(String path) {
|
|
return URL_ENCODER_XML.encode(path, StandardCharsets.UTF_8);
|
|
}
|
|
|
|
|
|
/**
|
|
* 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) {
|
|
return getRelativePath(request, false);
|
|
}
|
|
|
|
@Override
|
|
protected String getRelativePath(HttpServletRequest request, boolean allowEmptyPath) {
|
|
String pathInfo;
|
|
|
|
if (request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null) {
|
|
// For includes, get the info from the attributes
|
|
pathInfo = (String) request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
|
|
} else {
|
|
pathInfo = request.getPathInfo();
|
|
}
|
|
|
|
StringBuilder result = new StringBuilder();
|
|
if (pathInfo != null) {
|
|
result.append(pathInfo);
|
|
}
|
|
if (result.length() == 0) {
|
|
result.append('/');
|
|
}
|
|
|
|
return result.toString();
|
|
}
|
|
|
|
|
|
/**
|
|
* Determines the prefix for standard directory GET listings.
|
|
*/
|
|
@Override
|
|
protected String getPathPrefix(final HttpServletRequest request) {
|
|
// Repeat the servlet path (e.g. /webdav/) in the listing path
|
|
String contextPath = request.getContextPath();
|
|
if (request.getServletPath() != null) {
|
|
contextPath = contextPath + request.getServletPath();
|
|
}
|
|
return contextPath;
|
|
}
|
|
|
|
|
|
/**
|
|
* OPTIONS Method.
|
|
*
|
|
* @param req The Servlet request
|
|
* @param resp The Servlet response
|
|
* @throws ServletException If an error occurs
|
|
* @throws IOException If an IO error occurs
|
|
*/
|
|
@Override
|
|
protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
|
|
throws ServletException, IOException {
|
|
|
|
resp.addHeader("DAV", "1,2");
|
|
resp.addHeader("Allow", determineMethodsAllowed(req));
|
|
resp.addHeader("MS-Author-Via", "DAV");
|
|
}
|
|
|
|
|
|
/**
|
|
* PROPFIND Method.
|
|
* @param req The Servlet request
|
|
* @param resp The Servlet response
|
|
* @throws ServletException If an error occurs
|
|
* @throws IOException If an IO error occurs
|
|
*/
|
|
protected void doPropfind(HttpServletRequest req, HttpServletResponse resp)
|
|
throws ServletException, IOException {
|
|
|
|
if (!listings) {
|
|
sendNotAllowed(req, resp);
|
|
return;
|
|
}
|
|
|
|
String path = getRelativePath(req);
|
|
if (path.length() > 1 && path.endsWith("/"))
|
|
path = path.substring(0, path.length() - 1);
|
|
|
|
// Properties which are to be displayed.
|
|
Vector<String> properties = null;
|
|
// Propfind depth
|
|
int depth = maxDepth;
|
|
// Propfind type
|
|
int type = FIND_ALL_PROP;
|
|
|
|
String depthStr = req.getHeader("Depth");
|
|
|
|
if (depthStr == null) {
|
|
depth = maxDepth;
|
|
} else {
|
|
if (depthStr.equals("0")) {
|
|
depth = 0;
|
|
} else if (depthStr.equals("1")) {
|
|
depth = 1;
|
|
} else if (depthStr.equals("infinity")) {
|
|
depth = maxDepth;
|
|
}
|
|
}
|
|
|
|
Node propNode = null;
|
|
|
|
if (req.getContentLengthLong() > 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;
|
|
}
|
|
}
|
|
} catch (SAXException e) {
|
|
// Something went wrong - bad request
|
|
resp.sendError(WebdavStatus.SC_BAD_REQUEST);
|
|
return;
|
|
} catch (IOException e) {
|
|
// Something went wrong - bad request
|
|
resp.sendError(WebdavStatus.SC_BAD_REQUEST);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (type == FIND_BY_PROPERTY) {
|
|
properties = new Vector<>();
|
|
// propNode must be non-null if type == FIND_BY_PROPERTY
|
|
@SuppressWarnings("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 = null;
|
|
if (nodeName.indexOf(':') != -1) {
|
|
propertyName = nodeName.substring
|
|
(nodeName.indexOf(':') + 1);
|
|
} else {
|
|
propertyName = nodeName;
|
|
}
|
|
// href is a live property which is handled differently
|
|
properties.addElement(propertyName);
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
WebResource resource = resources.getResource(path);
|
|
|
|
if (!resource.exists()) {
|
|
int slash = path.lastIndexOf('/');
|
|
if (slash != -1) {
|
|
String parentPath = path.substring(0, slash);
|
|
Vector<String> currentLockNullResources =
|
|
lockNullResources.get(parentPath);
|
|
if (currentLockNullResources != null) {
|
|
Enumeration<String> lockNullResourcesList =
|
|
currentLockNullResources.elements();
|
|
while (lockNullResourcesList.hasMoreElements()) {
|
|
String lockNullPath =
|
|
lockNullResourcesList.nextElement();
|
|
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("D", DEFAULT_NAMESPACE,
|
|
"multistatus", XMLWriter.OPENING);
|
|
parseLockNullProperties
|
|
(req, generatedXML, lockNullPath, type,
|
|
properties);
|
|
generatedXML.writeElement("D", "multistatus",
|
|
XMLWriter.CLOSING);
|
|
generatedXML.sendData();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!resource.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("D", DEFAULT_NAMESPACE, "multistatus",
|
|
XMLWriter.OPENING);
|
|
|
|
if (depth == 0) {
|
|
parseProperties(req, generatedXML, path, type,
|
|
properties);
|
|
} else {
|
|
// The stack always contains the object of the current level
|
|
Stack<String> stack = new Stack<>();
|
|
stack.push(path);
|
|
|
|
// Stack of the objects one level below
|
|
Stack<String> stackBelow = new Stack<>();
|
|
|
|
while ((!stack.isEmpty()) && (depth >= 0)) {
|
|
|
|
String currentPath = stack.pop();
|
|
parseProperties(req, generatedXML, currentPath,
|
|
type, properties);
|
|
|
|
resource = resources.getResource(currentPath);
|
|
|
|
if (resource.isDirectory() && (depth > 0)) {
|
|
|
|
String[] entries = resources.list(currentPath);
|
|
for (String entry : entries) {
|
|
String newPath = currentPath;
|
|
if (!(newPath.endsWith("/")))
|
|
newPath += "/";
|
|
newPath += entry;
|
|
stackBelow.push(newPath);
|
|
}
|
|
|
|
// Displaying the lock-null resources present in that
|
|
// collection
|
|
String lockPath = currentPath;
|
|
if (lockPath.endsWith("/"))
|
|
lockPath =
|
|
lockPath.substring(0, lockPath.length() - 1);
|
|
Vector<String> currentLockNullResources =
|
|
lockNullResources.get(lockPath);
|
|
if (currentLockNullResources != null) {
|
|
Enumeration<String> lockNullResourcesList =
|
|
currentLockNullResources.elements();
|
|
while (lockNullResourcesList.hasMoreElements()) {
|
|
String lockNullPath =
|
|
lockNullResourcesList.nextElement();
|
|
parseLockNullProperties
|
|
(req, generatedXML, lockNullPath, type,
|
|
properties);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (stack.isEmpty()) {
|
|
depth--;
|
|
stack = stackBelow;
|
|
stackBelow = new Stack<>();
|
|
}
|
|
|
|
generatedXML.sendData();
|
|
|
|
}
|
|
}
|
|
|
|
generatedXML.writeElement("D", "multistatus", XMLWriter.CLOSING);
|
|
|
|
generatedXML.sendData();
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* PROPPATCH Method.
|
|
* @param req The Servlet request
|
|
* @param resp The Servlet response
|
|
* @throws IOException If an IO error occurs
|
|
*/
|
|
protected void doProppatch(HttpServletRequest req, HttpServletResponse resp)
|
|
throws 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.
|
|
* @param req The Servlet request
|
|
* @param resp The Servlet response
|
|
* @throws ServletException If an error occurs
|
|
* @throws IOException If an IO error occurs
|
|
*/
|
|
protected void doMkcol(HttpServletRequest req, HttpServletResponse resp)
|
|
throws ServletException, IOException {
|
|
|
|
String path = getRelativePath(req);
|
|
|
|
WebResource resource = resources.getResource(path);
|
|
|
|
// Can't create a collection if a resource already exists at the given
|
|
// path
|
|
if (resource.exists()) {
|
|
sendNotAllowed(req, resp);
|
|
return;
|
|
}
|
|
|
|
if (readOnly) {
|
|
resp.sendError(WebdavStatus.SC_FORBIDDEN);
|
|
return;
|
|
}
|
|
|
|
if (isLocked(req)) {
|
|
resp.sendError(WebdavStatus.SC_LOCKED);
|
|
return;
|
|
}
|
|
|
|
if (req.getContentLengthLong() > 0) {
|
|
DocumentBuilder documentBuilder = getDocumentBuilder();
|
|
try {
|
|
// Document document =
|
|
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_UNSUPPORTED_MEDIA_TYPE);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (resources.mkdir(path)) {
|
|
resp.setStatus(WebdavStatus.SC_CREATED);
|
|
// Removing any lock-null resource which would be present
|
|
lockNullResources.remove(path);
|
|
} else {
|
|
resp.sendError(WebdavStatus.SC_CONFLICT,
|
|
WebdavStatus.getStatusText
|
|
(WebdavStatus.SC_CONFLICT));
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* DELETE Method.
|
|
* @param req The Servlet request
|
|
* @param resp The Servlet response
|
|
* @throws ServletException If an error occurs
|
|
* @throws IOException If an IO error occurs
|
|
*/
|
|
@Override
|
|
protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
|
|
throws ServletException, IOException {
|
|
|
|
if (readOnly) {
|
|
sendNotAllowed(req, resp);
|
|
return;
|
|
}
|
|
|
|
if (isLocked(req)) {
|
|
resp.sendError(WebdavStatus.SC_LOCKED);
|
|
return;
|
|
}
|
|
|
|
deleteResource(req, resp);
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* Process a PUT 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
|
|
*/
|
|
@Override
|
|
protected void doPut(HttpServletRequest req, HttpServletResponse resp)
|
|
throws ServletException, IOException {
|
|
|
|
if (isLocked(req)) {
|
|
resp.sendError(WebdavStatus.SC_LOCKED);
|
|
return;
|
|
}
|
|
|
|
String path = getRelativePath(req);
|
|
WebResource resource = resources.getResource(path);
|
|
if (resource.isDirectory()) {
|
|
sendNotAllowed(req, resp);
|
|
return;
|
|
}
|
|
|
|
super.doPut(req, resp);
|
|
|
|
// Removing any lock-null resource which would be present
|
|
lockNullResources.remove(path);
|
|
|
|
}
|
|
|
|
/**
|
|
* COPY Method.
|
|
* @param req The Servlet request
|
|
* @param resp The Servlet response
|
|
* @throws IOException If an IO error occurs
|
|
*/
|
|
protected void doCopy(HttpServletRequest req, HttpServletResponse resp)
|
|
throws IOException {
|
|
|
|
if (readOnly) {
|
|
resp.sendError(WebdavStatus.SC_FORBIDDEN);
|
|
return;
|
|
}
|
|
|
|
copyResource(req, resp);
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* MOVE Method.
|
|
* @param req The Servlet request
|
|
* @param resp The Servlet response
|
|
* @throws IOException If an IO error occurs
|
|
*/
|
|
protected void doMove(HttpServletRequest req, HttpServletResponse resp)
|
|
throws 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.
|
|
* @param req The Servlet request
|
|
* @param resp The Servlet response
|
|
* @throws ServletException If an error occurs
|
|
* @throws IOException If an IO error occurs
|
|
*/
|
|
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(maxDepth);
|
|
|
|
// Parsing lock request
|
|
|
|
// Parsing depth header
|
|
|
|
String depthStr = req.getHeader("Depth");
|
|
|
|
if (depthStr == null) {
|
|
lock.depth = maxDepth;
|
|
} else {
|
|
if (depthStr.equals("0")) {
|
|
lock.depth = 0;
|
|
} else {
|
|
lock.depth = maxDepth;
|
|
}
|
|
}
|
|
|
|
// 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 (lockDurationStr.equalsIgnoreCase("infinity")) {
|
|
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 * 1000);
|
|
|
|
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 e) {
|
|
lockRequestType = LOCK_REFRESH;
|
|
} catch (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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (lock.scope == null) {
|
|
// Bad request
|
|
resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
|
|
}
|
|
|
|
} else {
|
|
// Bad request
|
|
resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
|
|
}
|
|
|
|
if (lockTypeNode != null) {
|
|
|
|
childList = lockTypeNode.getChildNodes();
|
|
for (int i=0; i < childList.getLength(); i++) {
|
|
Node currentNode = childList.item(i);
|
|
switch (currentNode.getNodeType()) {
|
|
case Node.TEXT_NODE:
|
|
break;
|
|
case Node.ELEMENT_NODE:
|
|
String tempType = currentNode.getNodeName();
|
|
if (tempType.indexOf(':') != -1) {
|
|
lock.type =
|
|
tempType.substring(tempType.indexOf(':') + 1);
|
|
} else {
|
|
lock.type = tempType;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (lock.type == null) {
|
|
// Bad request
|
|
resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
|
|
}
|
|
|
|
} else {
|
|
// Bad request
|
|
resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
|
|
}
|
|
|
|
if (lockOwnerNode != null) {
|
|
|
|
childList = lockOwnerNode.getChildNodes();
|
|
for (int i=0; i < childList.getLength(); i++) {
|
|
Node currentNode = childList.item(i);
|
|
switch (currentNode.getNodeType()) {
|
|
case Node.TEXT_NODE:
|
|
lock.owner += currentNode.getNodeValue();
|
|
break;
|
|
case Node.ELEMENT_NODE:
|
|
strWriter = new StringWriter();
|
|
domWriter = new DOMWriter(strWriter);
|
|
domWriter.print(currentNode);
|
|
lock.owner += strWriter.toString();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (lock.owner == null) {
|
|
// Bad request
|
|
resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
|
|
}
|
|
|
|
} else {
|
|
lock.owner = "";
|
|
}
|
|
|
|
}
|
|
|
|
String path = getRelativePath(req);
|
|
|
|
lock.path = path;
|
|
|
|
WebResource resource = resources.getResource(path);
|
|
|
|
Enumeration<LockInfo> locksList = null;
|
|
|
|
if (lockRequestType == LOCK_CREATION) {
|
|
|
|
// Generating lock id
|
|
String lockTokenStr = req.getServletPath() + "-" + lock.type + "-"
|
|
+ lock.scope + "-" + req.getUserPrincipal() + "-"
|
|
+ lock.depth + "-" + lock.owner + "-" + lock.tokens + "-"
|
|
+ lock.expiresAt + "-" + System.currentTimeMillis() + "-"
|
|
+ secret;
|
|
String lockToken = MD5Encoder.encode(ConcurrentMessageDigest.digestMD5(
|
|
lockTokenStr.getBytes(StandardCharsets.ISO_8859_1)));
|
|
|
|
if (resource.isDirectory() && lock.depth == maxDepth) {
|
|
|
|
// Locking a collection (and all its member resources)
|
|
|
|
// Checking if a child resource of this collection is
|
|
// already locked
|
|
Vector<String> lockPaths = new Vector<>();
|
|
locksList = collectionLocks.elements();
|
|
while (locksList.hasMoreElements()) {
|
|
LockInfo currentLock = locksList.nextElement();
|
|
if (currentLock.hasExpired()) {
|
|
resourceLocks.remove(currentLock.path);
|
|
continue;
|
|
}
|
|
if ( (currentLock.path.startsWith(lock.path)) &&
|
|
((currentLock.isExclusive()) ||
|
|
(lock.isExclusive())) ) {
|
|
// A child collection of this collection is locked
|
|
lockPaths.addElement(currentLock.path);
|
|
}
|
|
}
|
|
locksList = resourceLocks.elements();
|
|
while (locksList.hasMoreElements()) {
|
|
LockInfo currentLock = locksList.nextElement();
|
|
if (currentLock.hasExpired()) {
|
|
resourceLocks.remove(currentLock.path);
|
|
continue;
|
|
}
|
|
if ( (currentLock.path.startsWith(lock.path)) &&
|
|
((currentLock.isExclusive()) ||
|
|
(lock.isExclusive())) ) {
|
|
// A child resource of this collection is locked
|
|
lockPaths.addElement(currentLock.path);
|
|
}
|
|
}
|
|
|
|
if (!lockPaths.isEmpty()) {
|
|
|
|
// One of the child paths was locked
|
|
// We generate a multistatus error report
|
|
|
|
Enumeration<String> lockPathsList = lockPaths.elements();
|
|
|
|
resp.setStatus(WebdavStatus.SC_CONFLICT);
|
|
|
|
XMLWriter generatedXML = new XMLWriter();
|
|
generatedXML.writeXMLHeader();
|
|
|
|
generatedXML.writeElement("D", DEFAULT_NAMESPACE,
|
|
"multistatus", XMLWriter.OPENING);
|
|
|
|
while (lockPathsList.hasMoreElements()) {
|
|
generatedXML.writeElement("D", "response",
|
|
XMLWriter.OPENING);
|
|
generatedXML.writeElement("D", "href",
|
|
XMLWriter.OPENING);
|
|
generatedXML.writeText(lockPathsList.nextElement());
|
|
generatedXML.writeElement("D", "href",
|
|
XMLWriter.CLOSING);
|
|
generatedXML.writeElement("D", "status",
|
|
XMLWriter.OPENING);
|
|
generatedXML
|
|
.writeText("HTTP/1.1 " + WebdavStatus.SC_LOCKED
|
|
+ " " + WebdavStatus
|
|
.getStatusText(WebdavStatus.SC_LOCKED));
|
|
generatedXML.writeElement("D", "status",
|
|
XMLWriter.CLOSING);
|
|
|
|
generatedXML.writeElement("D", "response",
|
|
XMLWriter.CLOSING);
|
|
}
|
|
|
|
generatedXML.writeElement("D", "multistatus",
|
|
XMLWriter.CLOSING);
|
|
|
|
Writer writer = resp.getWriter();
|
|
writer.write(generatedXML.toString());
|
|
writer.close();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
boolean addLock = true;
|
|
|
|
// Checking if there is already a shared lock on this path
|
|
locksList = collectionLocks.elements();
|
|
while (locksList.hasMoreElements()) {
|
|
|
|
LockInfo currentLock = locksList.nextElement();
|
|
if (currentLock.path.equals(lock.path)) {
|
|
|
|
if (currentLock.isExclusive()) {
|
|
resp.sendError(WebdavStatus.SC_LOCKED);
|
|
return;
|
|
} else {
|
|
if (lock.isExclusive()) {
|
|
resp.sendError(WebdavStatus.SC_LOCKED);
|
|
return;
|
|
}
|
|
}
|
|
|
|
currentLock.tokens.addElement(lockToken);
|
|
lock = currentLock;
|
|
addLock = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (addLock) {
|
|
lock.tokens.addElement(lockToken);
|
|
collectionLocks.addElement(lock);
|
|
}
|
|
|
|
} else {
|
|
|
|
// Locking a single resource
|
|
|
|
// Retrieving an already existing lock on that resource
|
|
LockInfo presentLock = resourceLocks.get(lock.path);
|
|
if (presentLock != null) {
|
|
|
|
if ((presentLock.isExclusive()) || (lock.isExclusive())) {
|
|
// If either lock is exclusive, the lock can't be
|
|
// granted
|
|
resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
|
|
return;
|
|
} else {
|
|
presentLock.tokens.addElement(lockToken);
|
|
lock = presentLock;
|
|
}
|
|
|
|
} else {
|
|
|
|
lock.tokens.addElement(lockToken);
|
|
resourceLocks.put(lock.path, lock);
|
|
|
|
// Checking if a resource exists at this path
|
|
if (!resource.exists()) {
|
|
|
|
// "Creating" a lock-null resource
|
|
int slash = lock.path.lastIndexOf('/');
|
|
String parentPath = lock.path.substring(0, slash);
|
|
|
|
Vector<String> lockNulls =
|
|
lockNullResources.get(parentPath);
|
|
if (lockNulls == null) {
|
|
lockNulls = new Vector<>();
|
|
lockNullResources.put(parentPath, lockNulls);
|
|
}
|
|
|
|
lockNulls.addElement(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", "<opaquelocktoken:"
|
|
+ lockToken + ">");
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (lockRequestType == LOCK_REFRESH) {
|
|
|
|
String ifHeader = req.getHeader("If");
|
|
if (ifHeader == null)
|
|
ifHeader = "";
|
|
|
|
// Checking resource locks
|
|
|
|
LockInfo toRenew = resourceLocks.get(path);
|
|
Enumeration<String> tokenList = null;
|
|
|
|
if (toRenew != null) {
|
|
// At least one of the tokens of the locks must have been given
|
|
tokenList = toRenew.tokens.elements();
|
|
while (tokenList.hasMoreElements()) {
|
|
String token = tokenList.nextElement();
|
|
if (ifHeader.contains(token)) {
|
|
toRenew.expiresAt = lock.expiresAt;
|
|
lock = toRenew;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Checking inheritable collection locks
|
|
|
|
Enumeration<LockInfo> collectionLocksList =
|
|
collectionLocks.elements();
|
|
while (collectionLocksList.hasMoreElements()) {
|
|
toRenew = collectionLocksList.nextElement();
|
|
if (path.equals(toRenew.path)) {
|
|
|
|
tokenList = toRenew.tokens.elements();
|
|
while (tokenList.hasMoreElements()) {
|
|
String token = tokenList.nextElement();
|
|
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("D", DEFAULT_NAMESPACE, "prop",
|
|
XMLWriter.OPENING);
|
|
|
|
generatedXML.writeElement("D", "lockdiscovery", XMLWriter.OPENING);
|
|
|
|
lock.toXML(generatedXML);
|
|
|
|
generatedXML.writeElement("D", "lockdiscovery", XMLWriter.CLOSING);
|
|
|
|
generatedXML.writeElement("D", "prop", XMLWriter.CLOSING);
|
|
|
|
resp.setStatus(WebdavStatus.SC_OK);
|
|
resp.setContentType("text/xml; charset=UTF-8");
|
|
Writer writer = resp.getWriter();
|
|
writer.write(generatedXML.toString());
|
|
writer.close();
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* UNLOCK Method.
|
|
* @param req The Servlet request
|
|
* @param resp The Servlet response
|
|
* @throws IOException If an IO error occurs
|
|
*/
|
|
protected void doUnlock(HttpServletRequest req, HttpServletResponse resp)
|
|
throws 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);
|
|
Enumeration<String> tokenList = null;
|
|
if (lock != null) {
|
|
|
|
// At least one of the tokens of the locks must have been given
|
|
|
|
tokenList = lock.tokens.elements();
|
|
while (tokenList.hasMoreElements()) {
|
|
String token = tokenList.nextElement();
|
|
if (lockTokenHeader.contains(token)) {
|
|
lock.tokens.removeElement(token);
|
|
}
|
|
}
|
|
|
|
if (lock.tokens.isEmpty()) {
|
|
resourceLocks.remove(path);
|
|
// Removing any lock-null resource which would be present
|
|
lockNullResources.remove(path);
|
|
}
|
|
|
|
}
|
|
|
|
// Checking inheritable collection locks
|
|
|
|
Enumeration<LockInfo> collectionLocksList = collectionLocks.elements();
|
|
while (collectionLocksList.hasMoreElements()) {
|
|
lock = collectionLocksList.nextElement();
|
|
if (path.equals(lock.path)) {
|
|
|
|
tokenList = lock.tokens.elements();
|
|
while (tokenList.hasMoreElements()) {
|
|
String token = tokenList.nextElement();
|
|
if (lockTokenHeader.contains(token)) {
|
|
lock.tokens.removeElement(token);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (lock.tokens.isEmpty()) {
|
|
collectionLocks.removeElement(lock);
|
|
// Removing any lock-null resource which would be present
|
|
lockNullResources.remove(path);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
resp.setStatus(WebdavStatus.SC_NO_CONTENT);
|
|
|
|
}
|
|
|
|
// -------------------------------------------------------- Private Methods
|
|
|
|
/**
|
|
* 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 <code>true</code> 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 <code>true</code> if the resource is locked (and no appropriate
|
|
* lock token has been found for at least one of
|
|
* the non-shared locks which are present on the resource).
|
|
*/
|
|
private boolean isLocked(String path, String ifHeader) {
|
|
|
|
// Checking resource locks
|
|
|
|
LockInfo lock = resourceLocks.get(path);
|
|
Enumeration<String> tokenList = null;
|
|
if ((lock != null) && (lock.hasExpired())) {
|
|
resourceLocks.remove(path);
|
|
} else if (lock != null) {
|
|
|
|
// At least one of the tokens of the locks must have been given
|
|
|
|
tokenList = lock.tokens.elements();
|
|
boolean tokenMatch = false;
|
|
while (tokenList.hasMoreElements()) {
|
|
String token = tokenList.nextElement();
|
|
if (ifHeader.contains(token)) {
|
|
tokenMatch = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!tokenMatch)
|
|
return true;
|
|
|
|
}
|
|
|
|
// Checking inheritable collection locks
|
|
|
|
Enumeration<LockInfo> collectionLocksList = collectionLocks.elements();
|
|
while (collectionLocksList.hasMoreElements()) {
|
|
lock = collectionLocksList.nextElement();
|
|
if (lock.hasExpired()) {
|
|
collectionLocks.removeElement(lock);
|
|
} else if (path.startsWith(lock.path)) {
|
|
|
|
tokenList = lock.tokens.elements();
|
|
boolean tokenMatch = false;
|
|
while (tokenList.hasMoreElements()) {
|
|
String token = tokenList.nextElement();
|
|
if (ifHeader.contains(token)) {
|
|
tokenMatch = true;
|
|
break;
|
|
}
|
|
}
|
|
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
|
|
* @throws IOException If an IO error occurs
|
|
*/
|
|
private boolean copyResource(HttpServletRequest req,
|
|
HttpServletResponse resp)
|
|
throws 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 = UDecoder.URLDecode(destinationPath, StandardCharsets.UTF_8);
|
|
|
|
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);
|
|
|
|
// Check destination path to protect special subdirectories
|
|
if (isSpecialPath(destinationPath)) {
|
|
resp.sendError(WebdavStatus.SC_FORBIDDEN);
|
|
return false;
|
|
}
|
|
|
|
String path = getRelativePath(req);
|
|
|
|
if (destinationPath.equals(path)) {
|
|
resp.sendError(WebdavStatus.SC_FORBIDDEN);
|
|
return false;
|
|
}
|
|
|
|
// Parsing overwrite header
|
|
|
|
boolean overwrite = true;
|
|
String overwriteHeader = req.getHeader("Overwrite");
|
|
|
|
if (overwriteHeader != null) {
|
|
if (overwriteHeader.equalsIgnoreCase("T")) {
|
|
overwrite = true;
|
|
} else {
|
|
overwrite = false;
|
|
}
|
|
}
|
|
|
|
// Overwriting the destination
|
|
|
|
WebResource destination = resources.getResource(destinationPath);
|
|
|
|
if (overwrite) {
|
|
// Delete destination resource, if it exists
|
|
if (destination.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 (destination.exists()) {
|
|
resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Copying source to destination
|
|
|
|
Hashtable<String,Integer> errorList = new Hashtable<>();
|
|
|
|
boolean result = copyResource(errorList, path, destinationPath);
|
|
|
|
if ((!result) || (!errorList.isEmpty())) {
|
|
if (errorList.size() == 1) {
|
|
resp.sendError(errorList.elements().nextElement().intValue());
|
|
} else {
|
|
sendReport(req, resp, errorList);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Copy was successful
|
|
if (destination.exists()) {
|
|
resp.setStatus(WebdavStatus.SC_NO_CONTENT);
|
|
} else {
|
|
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 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
|
|
* @return <code>true</code> if the copy was successful
|
|
*/
|
|
private boolean copyResource(Hashtable<String,Integer> errorList,
|
|
String source, String dest) {
|
|
|
|
if (debug > 1)
|
|
log("Copy: " + source + " To: " + dest);
|
|
|
|
WebResource sourceResource = resources.getResource(source);
|
|
|
|
if (sourceResource.isDirectory()) {
|
|
if (!resources.mkdir(dest)) {
|
|
WebResource destResource = resources.getResource(dest);
|
|
if (!destResource.isDirectory()) {
|
|
errorList.put(dest, Integer.valueOf(WebdavStatus.SC_CONFLICT));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
String[] entries = resources.list(source);
|
|
for (String entry : entries) {
|
|
String childDest = dest;
|
|
if (!childDest.equals("/")) {
|
|
childDest += "/";
|
|
}
|
|
childDest += entry;
|
|
String childSrc = source;
|
|
if (!childSrc.equals("/")) {
|
|
childSrc += "/";
|
|
}
|
|
childSrc += entry;
|
|
copyResource(errorList, childSrc, childDest);
|
|
}
|
|
} else if (sourceResource.isFile()) {
|
|
WebResource destResource = resources.getResource(dest);
|
|
if (!destResource.exists() && !destResource.getWebappPath().endsWith("/")) {
|
|
int lastSlash = destResource.getWebappPath().lastIndexOf('/');
|
|
if (lastSlash > 0) {
|
|
String parent = destResource.getWebappPath().substring(0, lastSlash);
|
|
WebResource parentResource = resources.getResource(parent);
|
|
if (!parentResource.isDirectory()) {
|
|
errorList.put(source, Integer.valueOf(WebdavStatus.SC_CONFLICT));
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
// WebDAV Litmus test attempts to copy/move a file over a collection
|
|
// Need to remove trailing / from destination to enable test to pass
|
|
if (!destResource.exists() && dest.endsWith("/") && dest.length() > 1) {
|
|
// Convert destination name from collection (with trailing '/')
|
|
// to file (without trailing '/')
|
|
dest = dest.substring(0, dest.length() - 1);
|
|
}
|
|
try (InputStream is = sourceResource.getInputStream()) {
|
|
if (!resources.write(dest, is, false)) {
|
|
errorList.put(source, Integer.valueOf(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
|
|
return false;
|
|
}
|
|
} catch (IOException e) {
|
|
log(sm.getString("webdavservlet.inputstreamclosefail", source), e);
|
|
}
|
|
} else {
|
|
errorList.put(source, Integer.valueOf(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Delete a resource.
|
|
*
|
|
* @param req Servlet request
|
|
* @param resp Servlet response
|
|
* @return <code>true</code> if the delete is successful
|
|
* @throws IOException If an IO error occurs
|
|
*/
|
|
private boolean deleteResource(HttpServletRequest req,
|
|
HttpServletResponse resp)
|
|
throws 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
|
|
* @return <code>true</code> if the delete is successful
|
|
* @throws IOException If an IO error occurs
|
|
*/
|
|
private boolean deleteResource(String path, HttpServletRequest req,
|
|
HttpServletResponse resp, boolean setStatus)
|
|
throws IOException {
|
|
|
|
String ifHeader = req.getHeader("If");
|
|
if (ifHeader == null)
|
|
ifHeader = "";
|
|
|
|
String lockTokenHeader = req.getHeader("Lock-Token");
|
|
if (lockTokenHeader == null)
|
|
lockTokenHeader = "";
|
|
|
|
if (isLocked(path, ifHeader + lockTokenHeader)) {
|
|
resp.sendError(WebdavStatus.SC_LOCKED);
|
|
return false;
|
|
}
|
|
|
|
WebResource resource = resources.getResource(path);
|
|
|
|
if (!resource.exists()) {
|
|
resp.sendError(WebdavStatus.SC_NOT_FOUND);
|
|
return false;
|
|
}
|
|
|
|
if (!resource.isDirectory()) {
|
|
if (!resource.delete()) {
|
|
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
|
|
return false;
|
|
}
|
|
} else {
|
|
|
|
Hashtable<String,Integer> errorList = new Hashtable<>();
|
|
|
|
deleteCollection(req, path, errorList);
|
|
if (!resource.delete()) {
|
|
errorList.put(path, Integer.valueOf
|
|
(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 req The Servlet request
|
|
* @param path Path to the collection to be deleted
|
|
* @param errorList Contains the list of the errors which occurred
|
|
*/
|
|
private void deleteCollection(HttpServletRequest req,
|
|
String path,
|
|
Hashtable<String,Integer> errorList) {
|
|
|
|
if (debug > 1)
|
|
log("Delete:" + path);
|
|
|
|
// Prevent deletion of special subdirectories
|
|
if (isSpecialPath(path)) {
|
|
errorList.put(path, Integer.valueOf(WebdavStatus.SC_FORBIDDEN));
|
|
return;
|
|
}
|
|
|
|
String ifHeader = req.getHeader("If");
|
|
if (ifHeader == null)
|
|
ifHeader = "";
|
|
|
|
String lockTokenHeader = req.getHeader("Lock-Token");
|
|
if (lockTokenHeader == null)
|
|
lockTokenHeader = "";
|
|
|
|
String[] entries = resources.list(path);
|
|
|
|
for (String entry : entries) {
|
|
String childName = path;
|
|
if (!childName.equals("/"))
|
|
childName += "/";
|
|
childName += entry;
|
|
|
|
if (isLocked(childName, ifHeader + lockTokenHeader)) {
|
|
|
|
errorList.put(childName, Integer.valueOf(WebdavStatus.SC_LOCKED));
|
|
|
|
} else {
|
|
WebResource childResource = resources.getResource(childName);
|
|
if (childResource.isDirectory()) {
|
|
deleteCollection(req, childName, errorList);
|
|
}
|
|
|
|
if (!childResource.delete()) {
|
|
if (!childResource.isDirectory()) {
|
|
// If it's not a collection, then it's an unknown
|
|
// error
|
|
errorList.put(childName, Integer.valueOf(
|
|
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
|
|
* @throws IOException If an IO error occurs
|
|
*/
|
|
private void sendReport(HttpServletRequest req, HttpServletResponse resp,
|
|
Hashtable<String,Integer> errorList)
|
|
throws IOException {
|
|
|
|
resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
|
|
|
|
String absoluteUri = req.getRequestURI();
|
|
String relativePath = getRelativePath(req);
|
|
|
|
XMLWriter generatedXML = new XMLWriter();
|
|
generatedXML.writeXMLHeader();
|
|
|
|
generatedXML.writeElement("D", DEFAULT_NAMESPACE, "multistatus",
|
|
XMLWriter.OPENING);
|
|
|
|
Enumeration<String> pathList = errorList.keys();
|
|
while (pathList.hasMoreElements()) {
|
|
|
|
String errorPath = pathList.nextElement();
|
|
int errorCode = errorList.get(errorPath).intValue();
|
|
|
|
generatedXML.writeElement("D", "response", XMLWriter.OPENING);
|
|
|
|
generatedXML.writeElement("D", "href", XMLWriter.OPENING);
|
|
String toAppend = errorPath.substring(relativePath.length());
|
|
if (!toAppend.startsWith("/"))
|
|
toAppend = "/" + toAppend;
|
|
generatedXML.writeText(absoluteUri + toAppend);
|
|
generatedXML.writeElement("D", "href", XMLWriter.CLOSING);
|
|
generatedXML.writeElement("D", "status", XMLWriter.OPENING);
|
|
generatedXML.writeText("HTTP/1.1 " + errorCode + " "
|
|
+ WebdavStatus.getStatusText(errorCode));
|
|
generatedXML.writeElement("D", "status", XMLWriter.CLOSING);
|
|
|
|
generatedXML.writeElement("D", "response", XMLWriter.CLOSING);
|
|
|
|
}
|
|
|
|
generatedXML.writeElement("D", "multistatus", XMLWriter.CLOSING);
|
|
|
|
Writer writer = resp.getWriter();
|
|
writer.write(generatedXML.toString());
|
|
writer.close();
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* 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 propertiesVector If the propfind type is find properties by
|
|
* name, then this Vector contains those properties
|
|
*/
|
|
private void parseProperties(HttpServletRequest req,
|
|
XMLWriter generatedXML,
|
|
String path, int type,
|
|
Vector<String> propertiesVector) {
|
|
|
|
// Exclude any resource in the /WEB-INF and /META-INF subdirectories
|
|
if (isSpecialPath(path))
|
|
return;
|
|
|
|
WebResource resource = resources.getResource(path);
|
|
if (!resource.exists()) {
|
|
// File is in directory listing but doesn't appear to exist
|
|
// Broken symlink or odd permission settings?
|
|
return;
|
|
}
|
|
|
|
String href = req.getContextPath() + req.getServletPath();
|
|
if ((href.endsWith("/")) && (path.startsWith("/")))
|
|
href += path.substring(1);
|
|
else
|
|
href += path;
|
|
if (resource.isDirectory() && (!href.endsWith("/")))
|
|
href += "/";
|
|
|
|
String rewrittenUrl = rewriteUrl(href);
|
|
|
|
generatePropFindResponse(generatedXML, rewrittenUrl, path, type, propertiesVector,
|
|
resource.isFile(), false, resource.getCreation(), resource.getLastModified(),
|
|
resource.getContentLength(), getServletContext().getMimeType(resource.getName()),
|
|
resource.getETag());
|
|
}
|
|
|
|
|
|
/**
|
|
* Propfind helper method. Displays the properties of a lock-null resource.
|
|
*
|
|
* @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 propertiesVector If the propfind type is find properties by
|
|
* name, then this Vector contains those properties
|
|
*/
|
|
private void parseLockNullProperties(HttpServletRequest req,
|
|
XMLWriter generatedXML,
|
|
String path, int type,
|
|
Vector<String> propertiesVector) {
|
|
|
|
// Exclude any resource in the /WEB-INF and /META-INF subdirectories
|
|
if (isSpecialPath(path))
|
|
return;
|
|
|
|
// Retrieving the lock associated with the lock-null resource
|
|
LockInfo lock = resourceLocks.get(path);
|
|
|
|
if (lock == null)
|
|
return;
|
|
|
|
String absoluteUri = req.getRequestURI();
|
|
String relativePath = getRelativePath(req);
|
|
String toAppend = path.substring(relativePath.length());
|
|
if (!toAppend.startsWith("/"))
|
|
toAppend = "/" + toAppend;
|
|
|
|
String rewrittenUrl = rewriteUrl(RequestUtil.normalize(
|
|
absoluteUri + toAppend));
|
|
|
|
generatePropFindResponse(generatedXML, rewrittenUrl, path, type, propertiesVector,
|
|
true, true, lock.creationDate.getTime(), lock.creationDate.getTime(),
|
|
0, "", "");
|
|
}
|
|
|
|
|
|
private void generatePropFindResponse(XMLWriter generatedXML, String rewrittenUrl,
|
|
String path, int propFindType, Vector<String> propertiesVector, boolean isFile,
|
|
boolean isLockNull, long created, long lastModified, long contentLength,
|
|
String contentType, String eTag) {
|
|
|
|
generatedXML.writeElement("D", "response", XMLWriter.OPENING);
|
|
String status = "HTTP/1.1 " + WebdavStatus.SC_OK + " " +
|
|
WebdavStatus.getStatusText(WebdavStatus.SC_OK);
|
|
|
|
// Generating href element
|
|
generatedXML.writeElement("D", "href", XMLWriter.OPENING);
|
|
generatedXML.writeText(rewrittenUrl);
|
|
generatedXML.writeElement("D", "href", XMLWriter.CLOSING);
|
|
|
|
String resourceName = path;
|
|
int lastSlash = path.lastIndexOf('/');
|
|
if (lastSlash != -1)
|
|
resourceName = resourceName.substring(lastSlash + 1);
|
|
|
|
switch (propFindType) {
|
|
|
|
case FIND_ALL_PROP :
|
|
|
|
generatedXML.writeElement("D", "propstat", XMLWriter.OPENING);
|
|
generatedXML.writeElement("D", "prop", XMLWriter.OPENING);
|
|
|
|
generatedXML.writeProperty("D", "creationdate", getISOCreationDate(created));
|
|
generatedXML.writeElement("D", "displayname", XMLWriter.OPENING);
|
|
generatedXML.writeData(resourceName);
|
|
generatedXML.writeElement("D", "displayname", XMLWriter.CLOSING);
|
|
if (isFile) {
|
|
generatedXML.writeProperty("D", "getlastmodified",
|
|
FastHttpDateFormat.formatDate(lastModified));
|
|
generatedXML.writeProperty("D", "getcontentlength", Long.toString(contentLength));
|
|
if (contentType != null) {
|
|
generatedXML.writeProperty("D", "getcontenttype", contentType);
|
|
}
|
|
generatedXML.writeProperty("D", "getetag", eTag);
|
|
if (isLockNull) {
|
|
generatedXML.writeElement("D", "resourcetype", XMLWriter.OPENING);
|
|
generatedXML.writeElement("D", "lock-null", XMLWriter.NO_CONTENT);
|
|
generatedXML.writeElement("D", "resourcetype", XMLWriter.CLOSING);
|
|
} else {
|
|
generatedXML.writeElement("D", "resourcetype", XMLWriter.NO_CONTENT);
|
|
}
|
|
} else {
|
|
generatedXML.writeElement("D", "resourcetype", XMLWriter.OPENING);
|
|
generatedXML.writeElement("D", "collection", XMLWriter.NO_CONTENT);
|
|
generatedXML.writeElement("D", "resourcetype", XMLWriter.CLOSING);
|
|
}
|
|
|
|
generatedXML.writeProperty("D", "source", "");
|
|
|
|
String supportedLocks = "<D:lockentry>"
|
|
+ "<D:lockscope><D:exclusive/></D:lockscope>"
|
|
+ "<D:locktype><D:write/></D:locktype>"
|
|
+ "</D:lockentry>" + "<D:lockentry>"
|
|
+ "<D:lockscope><D:shared/></D:lockscope>"
|
|
+ "<D:locktype><D:write/></D:locktype>"
|
|
+ "</D:lockentry>";
|
|
generatedXML.writeElement("D", "supportedlock", XMLWriter.OPENING);
|
|
generatedXML.writeText(supportedLocks);
|
|
generatedXML.writeElement("D", "supportedlock", XMLWriter.CLOSING);
|
|
|
|
generateLockDiscovery(path, generatedXML);
|
|
|
|
generatedXML.writeElement("D", "prop", XMLWriter.CLOSING);
|
|
generatedXML.writeElement("D", "status", XMLWriter.OPENING);
|
|
generatedXML.writeText(status);
|
|
generatedXML.writeElement("D", "status", XMLWriter.CLOSING);
|
|
generatedXML.writeElement("D", "propstat", XMLWriter.CLOSING);
|
|
|
|
break;
|
|
|
|
case FIND_PROPERTY_NAMES :
|
|
|
|
generatedXML.writeElement("D", "propstat", XMLWriter.OPENING);
|
|
generatedXML.writeElement("D", "prop", XMLWriter.OPENING);
|
|
|
|
generatedXML.writeElement("D", "creationdate", XMLWriter.NO_CONTENT);
|
|
generatedXML.writeElement("D", "displayname", XMLWriter.NO_CONTENT);
|
|
if (isFile) {
|
|
generatedXML.writeElement("D", "getcontentlanguage", XMLWriter.NO_CONTENT);
|
|
generatedXML.writeElement("D", "getcontentlength", XMLWriter.NO_CONTENT);
|
|
generatedXML.writeElement("D", "getcontenttype", XMLWriter.NO_CONTENT);
|
|
generatedXML.writeElement("D", "getetag", XMLWriter.NO_CONTENT);
|
|
generatedXML.writeElement("D", "getlastmodified", XMLWriter.NO_CONTENT);
|
|
}
|
|
generatedXML.writeElement("D", "resourcetype", XMLWriter.NO_CONTENT);
|
|
generatedXML.writeElement("D", "source", XMLWriter.NO_CONTENT);
|
|
generatedXML.writeElement("D", "lockdiscovery", XMLWriter.NO_CONTENT);
|
|
|
|
generatedXML.writeElement("D", "prop", XMLWriter.CLOSING);
|
|
generatedXML.writeElement("D", "status", XMLWriter.OPENING);
|
|
generatedXML.writeText(status);
|
|
generatedXML.writeElement("D", "status", XMLWriter.CLOSING);
|
|
generatedXML.writeElement("D", "propstat", XMLWriter.CLOSING);
|
|
|
|
break;
|
|
|
|
case FIND_BY_PROPERTY :
|
|
|
|
Vector<String> propertiesNotFound = new Vector<>();
|
|
|
|
// Parse the list of properties
|
|
|
|
generatedXML.writeElement("D", "propstat", XMLWriter.OPENING);
|
|
generatedXML.writeElement("D", "prop", XMLWriter.OPENING);
|
|
|
|
Enumeration<String> properties = propertiesVector.elements();
|
|
|
|
while (properties.hasMoreElements()) {
|
|
|
|
String property = properties.nextElement();
|
|
|
|
if (property.equals("creationdate")) {
|
|
generatedXML.writeProperty("D", "creationdate", getISOCreationDate(created));
|
|
} else if (property.equals("displayname")) {
|
|
generatedXML.writeElement("D", "displayname", XMLWriter.OPENING);
|
|
generatedXML.writeData(resourceName);
|
|
generatedXML.writeElement("D", "displayname", XMLWriter.CLOSING);
|
|
} else if (property.equals("getcontentlanguage")) {
|
|
if (isFile) {
|
|
generatedXML.writeElement("D", "getcontentlanguage",
|
|
XMLWriter.NO_CONTENT);
|
|
} else {
|
|
propertiesNotFound.addElement(property);
|
|
}
|
|
} else if (property.equals("getcontentlength")) {
|
|
if (isFile) {
|
|
generatedXML.writeProperty("D", "getcontentlength",
|
|
Long.toString(contentLength));
|
|
} else {
|
|
propertiesNotFound.addElement(property);
|
|
}
|
|
} else if (property.equals("getcontenttype")) {
|
|
if (isFile) {
|
|
generatedXML.writeProperty("D", "getcontenttype", contentType);
|
|
} else {
|
|
propertiesNotFound.addElement(property);
|
|
}
|
|
} else if (property.equals("getetag")) {
|
|
if (isFile) {
|
|
generatedXML.writeProperty("D", "getetag", eTag);
|
|
} else {
|
|
propertiesNotFound.addElement(property);
|
|
}
|
|
} else if (property.equals("getlastmodified")) {
|
|
if (isFile) {
|
|
generatedXML.writeProperty("D", "getlastmodified",
|
|
FastHttpDateFormat.formatDate(lastModified));
|
|
} else {
|
|
propertiesNotFound.addElement(property);
|
|
}
|
|
} else if (property.equals("resourcetype")) {
|
|
if (isFile) {
|
|
if(isLockNull) {
|
|
generatedXML.writeElement("D", "resourcetype", XMLWriter.OPENING);
|
|
generatedXML.writeElement("D", "lock-null", XMLWriter.NO_CONTENT);
|
|
generatedXML.writeElement("D", "resourcetype", XMLWriter.CLOSING);
|
|
} else {
|
|
generatedXML.writeElement("D", "resourcetype", XMLWriter.NO_CONTENT);
|
|
}
|
|
} else {
|
|
generatedXML.writeElement("D", "resourcetype", XMLWriter.OPENING);
|
|
generatedXML.writeElement("D", "collection", XMLWriter.NO_CONTENT);
|
|
generatedXML.writeElement("D", "resourcetype",XMLWriter.CLOSING);
|
|
}
|
|
} else if (property.equals("source")) {
|
|
generatedXML.writeProperty("D", "source", "");
|
|
} else if (property.equals("supportedlock")) {
|
|
supportedLocks = "<D:lockentry>"
|
|
+ "<D:lockscope><D:exclusive/></D:lockscope>"
|
|
+ "<D:locktype><D:write/></D:locktype>"
|
|
+ "</D:lockentry>" + "<D:lockentry>"
|
|
+ "<D:lockscope><D:shared/></D:lockscope>"
|
|
+ "<D:locktype><D:write/></D:locktype>"
|
|
+ "</D:lockentry>";
|
|
generatedXML.writeElement("D", "supportedlock", XMLWriter.OPENING);
|
|
generatedXML.writeText(supportedLocks);
|
|
generatedXML.writeElement("D", "supportedlock", XMLWriter.CLOSING);
|
|
} else if (property.equals("lockdiscovery")) {
|
|
if (!generateLockDiscovery(path, generatedXML))
|
|
propertiesNotFound.addElement(property);
|
|
} else {
|
|
propertiesNotFound.addElement(property);
|
|
}
|
|
|
|
}
|
|
|
|
generatedXML.writeElement("D", "prop", XMLWriter.CLOSING);
|
|
generatedXML.writeElement("D", "status", XMLWriter.OPENING);
|
|
generatedXML.writeText(status);
|
|
generatedXML.writeElement("D", "status", XMLWriter.CLOSING);
|
|
generatedXML.writeElement("D", "propstat", XMLWriter.CLOSING);
|
|
|
|
Enumeration<String> propertiesNotFoundList = propertiesNotFound.elements();
|
|
|
|
if (propertiesNotFoundList.hasMoreElements()) {
|
|
|
|
status = "HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND + " " +
|
|
WebdavStatus.getStatusText(WebdavStatus.SC_NOT_FOUND);
|
|
|
|
generatedXML.writeElement("D", "propstat", XMLWriter.OPENING);
|
|
generatedXML.writeElement("D", "prop", XMLWriter.OPENING);
|
|
|
|
while (propertiesNotFoundList.hasMoreElements()) {
|
|
generatedXML.writeElement("D", propertiesNotFoundList.nextElement(),
|
|
XMLWriter.NO_CONTENT);
|
|
}
|
|
|
|
generatedXML.writeElement("D", "prop", XMLWriter.CLOSING);
|
|
generatedXML.writeElement("D", "status", XMLWriter.OPENING);
|
|
generatedXML.writeText(status);
|
|
generatedXML.writeElement("D", "status", XMLWriter.CLOSING);
|
|
generatedXML.writeElement("D", "propstat", XMLWriter.CLOSING);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
generatedXML.writeElement("D", "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 <code>true</code> if at least one lock was displayed
|
|
*/
|
|
private boolean generateLockDiscovery
|
|
(String path, XMLWriter generatedXML) {
|
|
|
|
LockInfo resourceLock = resourceLocks.get(path);
|
|
Enumeration<LockInfo> collectionLocksList = collectionLocks.elements();
|
|
|
|
boolean wroteStart = false;
|
|
|
|
if (resourceLock != null) {
|
|
wroteStart = true;
|
|
generatedXML.writeElement("D", "lockdiscovery", XMLWriter.OPENING);
|
|
resourceLock.toXML(generatedXML);
|
|
}
|
|
|
|
while (collectionLocksList.hasMoreElements()) {
|
|
LockInfo currentLock = collectionLocksList.nextElement();
|
|
if (path.startsWith(currentLock.path)) {
|
|
if (!wroteStart) {
|
|
wroteStart = true;
|
|
generatedXML.writeElement("D", "lockdiscovery",
|
|
XMLWriter.OPENING);
|
|
}
|
|
currentLock.toXML(generatedXML);
|
|
}
|
|
}
|
|
|
|
if (wroteStart) {
|
|
generatedXML.writeElement("D", "lockdiscovery", XMLWriter.CLOSING);
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* Get creation date in ISO format.
|
|
* @return the formatted creation date
|
|
*/
|
|
private String getISOCreationDate(long creationDate) {
|
|
return creationDateFormat.format(new Date(creationDate));
|
|
}
|
|
|
|
/**
|
|
* Determines the methods normally allowed for the resource.
|
|
*
|
|
* @param req The Servlet request
|
|
*
|
|
* @return The allowed HTTP methods
|
|
*/
|
|
@Override
|
|
protected String determineMethodsAllowed(HttpServletRequest req) {
|
|
|
|
|
|
WebResource resource = resources.getResource(getRelativePath(req));
|
|
|
|
// These methods are always allowed. They may return a 404 (not a 405)
|
|
// if the resource does not exist.
|
|
StringBuilder methodsAllowed = new StringBuilder(
|
|
"OPTIONS, GET, POST, HEAD");
|
|
|
|
if (!readOnly) {
|
|
methodsAllowed.append(", DELETE");
|
|
if (!resource.isDirectory()) {
|
|
methodsAllowed.append(", PUT");
|
|
}
|
|
}
|
|
|
|
// Trace - assume disabled unless we can prove otherwise
|
|
if (req instanceof RequestFacade &&
|
|
((RequestFacade) req).getAllowTrace()) {
|
|
methodsAllowed.append(", TRACE");
|
|
}
|
|
|
|
methodsAllowed.append(", LOCK, UNLOCK, PROPPATCH, COPY, MOVE");
|
|
|
|
if (listings) {
|
|
methodsAllowed.append(", PROPFIND");
|
|
}
|
|
|
|
if (!resource.exists()) {
|
|
methodsAllowed.append(", MKCOL");
|
|
}
|
|
|
|
return methodsAllowed.toString();
|
|
}
|
|
|
|
|
|
// -------------------------------------------------- LockInfo Inner Class
|
|
|
|
/**
|
|
* Holds a lock information.
|
|
*/
|
|
private static class LockInfo implements Serializable {
|
|
|
|
private static final long serialVersionUID = 1L;
|
|
|
|
public LockInfo(int maxDepth) {
|
|
this.maxDepth = maxDepth;
|
|
}
|
|
|
|
|
|
// ------------------------------------------------- Instance Variables
|
|
|
|
private final int maxDepth;
|
|
|
|
String path = "/";
|
|
String type = "write";
|
|
String scope = "exclusive";
|
|
int depth = 0;
|
|
String owner = "";
|
|
Vector<String> tokens = new Vector<>();
|
|
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);
|
|
result.append("\nScope:");
|
|
result.append(scope);
|
|
result.append("\nDepth:");
|
|
result.append(depth);
|
|
result.append("\nOwner:");
|
|
result.append(owner);
|
|
result.append("\nExpiration:");
|
|
result.append(FastHttpDateFormat.formatDate(expiresAt));
|
|
Enumeration<String> tokensList = tokens.elements();
|
|
while (tokensList.hasMoreElements()) {
|
|
result.append("\nToken:");
|
|
result.append(tokensList.nextElement());
|
|
}
|
|
result.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 scope.equals("exclusive");
|
|
}
|
|
|
|
|
|
/**
|
|
* Get an XML representation of this lock token.
|
|
*
|
|
* @param generatedXML The XML write to which the fragment will be
|
|
* appended
|
|
*/
|
|
public void toXML(XMLWriter generatedXML) {
|
|
|
|
generatedXML.writeElement("D", "activelock", XMLWriter.OPENING);
|
|
|
|
generatedXML.writeElement("D", "locktype", XMLWriter.OPENING);
|
|
generatedXML.writeElement("D", type, XMLWriter.NO_CONTENT);
|
|
generatedXML.writeElement("D", "locktype", XMLWriter.CLOSING);
|
|
|
|
generatedXML.writeElement("D", "lockscope", XMLWriter.OPENING);
|
|
generatedXML.writeElement("D", scope, XMLWriter.NO_CONTENT);
|
|
generatedXML.writeElement("D", "lockscope", XMLWriter.CLOSING);
|
|
|
|
generatedXML.writeElement("D", "depth", XMLWriter.OPENING);
|
|
if (depth == maxDepth) {
|
|
generatedXML.writeText("Infinity");
|
|
} else {
|
|
generatedXML.writeText("0");
|
|
}
|
|
generatedXML.writeElement("D", "depth", XMLWriter.CLOSING);
|
|
|
|
generatedXML.writeElement("D", "owner", XMLWriter.OPENING);
|
|
generatedXML.writeText(owner);
|
|
generatedXML.writeElement("D", "owner", XMLWriter.CLOSING);
|
|
|
|
generatedXML.writeElement("D", "timeout", XMLWriter.OPENING);
|
|
long timeout = (expiresAt - System.currentTimeMillis()) / 1000;
|
|
generatedXML.writeText("Second-" + timeout);
|
|
generatedXML.writeElement("D", "timeout", XMLWriter.CLOSING);
|
|
|
|
generatedXML.writeElement("D", "locktoken", XMLWriter.OPENING);
|
|
Enumeration<String> tokensList = tokens.elements();
|
|
while (tokensList.hasMoreElements()) {
|
|
generatedXML.writeElement("D", "href", XMLWriter.OPENING);
|
|
generatedXML.writeText("opaquelocktoken:"
|
|
+ tokensList.nextElement());
|
|
generatedXML.writeElement("D", "href", XMLWriter.CLOSING);
|
|
}
|
|
generatedXML.writeElement("D", "locktoken", XMLWriter.CLOSING);
|
|
|
|
generatedXML.writeElement("D", "activelock", XMLWriter.CLOSING);
|
|
}
|
|
}
|
|
|
|
|
|
// --------------------------------------------- WebdavResolver Inner Class
|
|
/**
|
|
* Work around for XML parsers that don't fully respect
|
|
* {@link DocumentBuilderFactory#setExpandEntityReferences(boolean)} when
|
|
* called with <code>false</code>. External references are filtered out for
|
|
* security reasons. See CVE-2007-5461.
|
|
*/
|
|
private static class WebdavResolver implements EntityResolver {
|
|
private ServletContext context;
|
|
|
|
public WebdavResolver(ServletContext theContext) {
|
|
context = theContext;
|
|
}
|
|
|
|
@Override
|
|
public InputSource resolveEntity (String publicId, String systemId) {
|
|
context.log(sm.getString("webdavservlet.enternalEntityIgnored",
|
|
publicId, systemId));
|
|
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<Integer,String> 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 = Integer.valueOf(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(Integer.valueOf(nKey), strVal);
|
|
}
|
|
|
|
}
|
|
|
|
|