/* * 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;
/** * 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>
*/ publicclass WebdavServlet extends DefaultServlet {
/** * Simple date format for the creation date ISO representation (partial).
*/ protectedstaticfinal ConcurrentDateFormat creationDateFormat = new ConcurrentDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US, TimeZone.getTimeZone("GMT"));
/** * Repository of the locks put on single resources. * <p> * Key : path <br> * Value : LockInfo
*/ privatefinal Map<String,LockInfo> resourceLocks = new ConcurrentHashMap<>();
/** * Repository of the lock-null resources. * <p> * Key : path of the collection containing the lock-null resource<br> * 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.
*/ privatefinal Map<String,List<String>> lockNullResources = new ConcurrentHashMap<>();
/** * List of the inheritable collection locks.
*/ privatefinal List<LockInfo> collectionLocks = Collections.synchronizedList(new ArrayList<>());
/** * 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.
*/ privateint maxDepth = 3;
/** * Is access allowed via WebDAV to the special paths (/WEB-INF and /META-INF)?
*/ privateboolean allowSpecialPaths = false;
// --------------------------------------------------------- Public Methods
/** * Handles the special WebDAV methods.
*/
@Override protectedvoid 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;
}
/** * 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
*/ privateboolean isSpecialPath(final String path) { return !allowSpecialPaths && (path.toUpperCase(Locale.ENGLISH).startsWith("/WEB-INF") ||
path.toUpperCase(Locale.ENGLISH).startsWith("/META-INF"));
}
if (!super.checkIfHeaders(request, response, resource)) { returnfalse;
}
// TODO : Checking the WebDAV If header returntrue;
}
/** * 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);
}
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 protectedvoid 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
*/ protectedvoid doPropfind(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if (!listings) {
sendNotAllowed(req, resp); return;
}
// Properties which are to be displayed.
List<String> properties = null; // Propfind depth int depth = maxDepth; // Propfind type int type = FIND_ALL_PROP;
// 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 | IOException e) { // Something went wrong - bad request
resp.sendError(WebdavStatus.SC_BAD_REQUEST); return;
}
}
if (type == FIND_BY_PROPERTY) {
properties = new ArrayList<>(); // 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.add(propertyName); break;
}
}
}
if (depth == 0) {
parseProperties(req, generatedXML, path, type, properties);
} else { // The stack always contains the object of the current level
Deque<String> stack = new ArrayDeque<>();
stack.addFirst(path);
// Stack of the objects one level below
Deque<String> stackBelow = new ArrayDeque<>();
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);
}
}
/** * 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 protectedvoid 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 protectedvoid doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if (isLocked(req)) {
resp.sendError(WebdavStatus.SC_LOCKED); return;
}
// Get the root element of the document
Element rootElement = document.getDocumentElement();
lockInfoNode = rootElement;
} catch (IOException | SAXException e) {
lockRequestType = LOCK_REFRESH;
}
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
List<String> lockPaths = new ArrayList<>();
Iterator<LockInfo> collectionLocksIterator = collectionLocks.iterator(); while (collectionLocksIterator.hasNext()) {
LockInfo currentLock = collectionLocksIterator.next(); if (currentLock.hasExpired()) {
collectionLocksIterator.remove(); continue;
} if (currentLock.path.startsWith(lock.path) && (currentLock.isExclusive() || lock.isExclusive())) { // A child collection of this collection is locked
lockPaths.add(currentLock.path);
}
} for (LockInfo currentLock : resourceLocks.values()) { 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.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();
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;
}
// 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);
lockNullResources.computeIfAbsent(parentPath, k -> new ArrayList<>()).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", "<opaquelocktoken:" + lockToken + ">");
}
}
}
if (toRenew != 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 collecionLock : collectionLocks) { if (path.equals(collecionLock.path)) { for (String token : collecionLock.tokens) { if (ifHeader.contains(token)) {
collecionLock.expiresAt = lock.expiresAt;
lock = collecionLock;
}
}
}
}
}
// 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);
LockInfo lock = resourceLocks.get(path); if (lock != null) {
// At least one of the tokens of the locks must have been given
Iterator<String> 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
Iterator<LockInfo> collectionLocksList = collectionLocks.iterator(); while (collectionLocksList.hasNext()) {
lock = collectionLocksList.next(); if (path.equals(lock.path)) {
Iterator<String> tokenList = lock.tokens.iterator(); while (tokenList.hasNext()) {
String token = tokenList.next(); if (lockTokenHeader.contains(token)) {
tokenList.remove(); break;
}
} if (lock.tokens.isEmpty()) {
collectionLocksList.remove(); // Removing any lock-null resource which would be present
lockNullResources.remove(path);
}
}
}
/** * 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).
*/ privateboolean isLocked(HttpServletRequest req) {
/** * 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).
*/ privateboolean isLocked(String path, String ifHeader) {
// Destination isn't allowed to use '.' or '..' segments if (!destinationPath.equals(RequestUtil.normalize(destinationPath))) {
resp.sendError(WebdavStatus.SC_BAD_REQUEST); returnfalse;
}
if (destinationUri.isAbsolute()) { // Scheme and host need to match if (!req.getScheme().equals(destinationUri.getScheme()) ||
!req.getServerName().equals(destinationUri.getHost())) {
resp.sendError(WebdavStatus.SC_FORBIDDEN); returnfalse;
} // Port needs to match too but handled separately as the logic is a // little more complicated if (req.getServerPort() != destinationUri.getPort()) { if (destinationUri.getPort() == -1 && ("http".equals(req.getScheme()) && req.getServerPort() == 80 || "https".equals(req.getScheme()) && req.getServerPort() == 443)) { // All good.
} else {
resp.sendError(WebdavStatus.SC_FORBIDDEN); returnfalse;
}
}
}
// Check destination path to protect special subdirectories if (isSpecialPath(destinationPath)) {
resp.sendError(WebdavStatus.SC_FORBIDDEN); returnfalse;
}
if (destinationPath.equals(path)) {
resp.sendError(WebdavStatus.SC_FORBIDDEN); returnfalse;
}
// Check src / dest are not sub-dirs of each other if (destinationPath.startsWith(path) && destinationPath.charAt(path.length()) == '/' ||
path.startsWith(destinationPath) && path.charAt(destinationPath.length()) == '/') {
resp.sendError(WebdavStatus.SC_FORBIDDEN); returnfalse;
}
// 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)) { returnfalse;
}
} 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); returnfalse;
}
}
// Copying source to destination
Map<String,Integer> errorList = new HashMap<>();
boolean result = copyResource(errorList, path, destinationPath);
if ((!result) || (!errorList.isEmpty())) { if (errorList.size() == 1) {
resp.sendError(errorList.values().iterator().next().intValue());
} else {
sendReport(req, resp, errorList);
} returnfalse;
}
// 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);
returntrue;
}
/** * Copy a collection. * * @param errorList Map 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
*/ privateboolean copyResource(Map<String,Integer> errorList, String source, String dest) {
if (debug > 1) {
log("Copy: " + source + " To: " + dest);
}
if (sourceResource.isDirectory()) { if (!resources.mkdir(dest)) {
WebResource destResource = resources.getResource(dest); if (!destResource.isDirectory()) {
errorList.put(dest, Integer.valueOf(WebdavStatus.SC_CONFLICT)); returnfalse;
}
}
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);
}
} elseif (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)); returnfalse;
}
}
} // 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)); returnfalse;
}
} catch (IOException e) {
log(sm.getString("webdavservlet.inputstreamclosefail", source), e);
}
} else {
errorList.put(source, Integer.valueOf(WebdavStatus.SC_INTERNAL_SERVER_ERROR)); returnfalse;
} returntrue;
}
/** * 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
*/ privateboolean 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
*/ privateboolean deleteResource(String path, HttpServletRequest req, HttpServletResponse resp, boolean setStatus) throws IOException {
if (!resource.exists()) {
resp.sendError(WebdavStatus.SC_NOT_FOUND); returnfalse;
}
if (!resource.isDirectory()) { if (!resource.delete()) {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); returnfalse;
}
} else {
Map<String,Integer> errorList = new HashMap<>();
deleteCollection(req, path, errorList); if (!resource.delete()) {
errorList.put(path, Integer.valueOf(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
}
if (!errorList.isEmpty()) {
sendReport(req, resp, errorList); returnfalse;
}
} if (setStatus) {
resp.setStatus(WebdavStatus.SC_NO_CONTENT);
} returntrue;
}
/** * 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
*/ privatevoid deleteCollection(HttpServletRequest req, String path, Map<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;
}
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
*/ privatevoid sendReport(HttpServletRequest req, HttpServletResponse resp, Map<String,Integer> errorList) throws IOException {
resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
XMLWriter generatedXML = new XMLWriter();
generatedXML.writeXMLHeader();
/** * 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 properties If the propfind type is find properties by name, then this List contains those properties
*/ privatevoid parseProperties(HttpServletRequest req, XMLWriter generatedXML, String path, int type,
List<String> properties) {
// 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;
}
/** * 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 properties If the propfind type is find properties by name, then this List contains those properties
*/ privatevoid parseLockNullProperties(HttpServletRequest req, XMLWriter generatedXML, String path, int type,
List<String> properties) {
// 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);
/** * 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
*/ privateboolean generateLockDiscovery(String path, XMLWriter generatedXML) {
/** * 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) {
// 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");
}
String path = "/";
String type = "write";
String scope = "exclusive"; int depth = 0;
String owner = "";
List<String> tokens = Collections.synchronizedList(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);
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)); for (String token : tokens) {
result.append("\nToken:");
result.append(token);
}
result.append("\n"); return result.toString();
}
/** * @return true if the lock has expired.
*/ publicboolean hasExpired() { return System.currentTimeMillis() > expiresAt;
}
/** * @return true if the lock is exclusive.
*/ publicboolean 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
*/ publicvoid toXML(XMLWriter generatedXML) {
// --------------------------------------------- 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.
*/ privatestaticclass WebdavResolver implements EntityResolver { private ServletContext context;
// -------------------------------------------------------- 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
*/ class WebdavStatus {
// ------------------------------------------------------ HTTP Status Codes
/** * Status code (200) indicating the request succeeded normally.
*/ publicstaticfinalint SC_OK = HttpServletResponse.SC_OK;
/** * Status code (201) indicating the request succeeded and created a new resource on the server.
*/ publicstaticfinalint SC_CREATED = HttpServletResponse.SC_CREATED;
/** * Status code (202) indicating that a request was accepted for processing, but was not completed.
*/ publicstaticfinalint SC_ACCEPTED = HttpServletResponse.SC_ACCEPTED;
/** * Status code (204) indicating that the request succeeded but that there was no new information to return.
*/ publicstaticfinalint 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.
*/ publicstaticfinalint 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.
*/ publicstaticfinalint 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.
*/ publicstaticfinalint SC_NOT_MODIFIED = HttpServletResponse.SC_NOT_MODIFIED;
/** * Status code (400) indicating the request sent by the client was syntactically incorrect.
*/ publicstaticfinalint SC_BAD_REQUEST = HttpServletResponse.SC_BAD_REQUEST;
/** * Status code (401) indicating that the request requires HTTP authentication.
*/ publicstaticfinalint SC_UNAUTHORIZED = HttpServletResponse.SC_UNAUTHORIZED;
/** * Status code (403) indicating the server understood the request but refused to fulfill it.
*/ publicstaticfinalint SC_FORBIDDEN = HttpServletResponse.SC_FORBIDDEN;
/** * Status code (404) indicating that the requested resource is not available.
*/ publicstaticfinalint SC_NOT_FOUND = HttpServletResponse.SC_NOT_FOUND;
/** * Status code (500) indicating an error inside the HTTP service which prevented it from fulfilling the request.
*/ publicstaticfinalint 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.
*/ publicstaticfinalint 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.
*/ publicstaticfinalint SC_BAD_GATEWAY = HttpServletResponse.SC_BAD_GATEWAY;
/** * Status code (503) indicating that the HTTP service is temporarily overloaded, and unable to handle the request.
*/ publicstaticfinalint 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.
*/ publicstaticfinalint SC_CONTINUE = HttpServletResponse.SC_CONTINUE;
/** * Status code (405) indicating the method specified is not allowed for the resource.
*/ publicstaticfinalint SC_METHOD_NOT_ALLOWED = HttpServletResponse.SC_METHOD_NOT_ALLOWED;
/** * Status code (409) indicating that the request could not be completed due to a conflict with the current state of * the resource.
*/ publicstaticfinalint SC_CONFLICT = HttpServletResponse.SC_CONFLICT;
/** * 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.
*/ publicstaticfinalint SC_PRECONDITION_FAILED = HttpServletResponse.SC_PRECONDITION_FAILED;
/** * 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.
*/ publicstaticfinalint SC_REQUEST_TOO_LONG = HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE;
/** * 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.
*/ publicstaticfinalint SC_UNSUPPORTED_MEDIA_TYPE = HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE;
// -------------------------------------------- Extended WebDav status code
/** * Status code (207) indicating that the response requires providing status for multiple independent operations.
*/ publicstaticfinalint 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.
*/ publicstaticfinalint 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.
*/ publicstaticfinalint 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.
*/ publicstaticfinalint 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.
*/ publicstaticfinalint SC_LOCKED = 423;
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.40 Sekunden
(vorverarbeitet am 2026-04-26)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.