/* * 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.olat.core.commons.services.webdav.servlets; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.RandomAccessFile; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import java.text.Normalizer; import java.util.Collection; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; import java.util.Iterator; import java.util.Locale; import java.util.Map; 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.olat.core.commons.services.webdav.WebDAVDispatcher; import org.olat.core.commons.services.webdav.WebDAVManager; import org.olat.core.commons.services.webdav.WebDAVModule; import org.olat.core.dispatcher.Dispatcher; import org.olat.core.helpers.Settings; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.UserSession; import org.olat.core.util.vfs.QuotaExceededException; import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.vfs.lock.LockInfo; import org.olat.core.util.vfs.lock.VFSLockManagerImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; 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 WebDAV level 2. All the basic HTTP requests * are handled by the DefaultServlet. The WebDAVServlet must not be used as the * default servlet (ie mapped to '/') as it will not work in this configuration. * <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. This <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 * @version $Id$ */ @Service("webDAVDispatcher") public class WebDAVDispatcherImpl extends DefaultDispatcher implements WebDAVDispatcher, Dispatcher { private static final long serialVersionUID = 1L; private static final OLog log = Tracing.createLoggerFor(WebDAVDispatcherImpl.class); // -------------------------------------------------------------- Constants private static final String METHOD_PROPFIND = "PROPFIND"; private static final String METHOD_PROPPATCH = "PROPPATCH"; private static final String METHOD_MKCOL = "MKCOL"; private static final String METHOD_COPY = "COPY"; private static final String METHOD_MOVE = "MOVE"; private static final String METHOD_LOCK = "LOCK"; private static final String METHOD_UNLOCK = "UNLOCK"; private static final String METHOD_DELETE = "DELETE"; private static final String METHOD_HEAD = "HEAD"; private static final String METHOD_GET = "GET"; private static final String METHOD_OPTIONS = "OPTIONS"; private static final String METHOD_POST = "POST"; private static final String METHOD_PUT = "PUT"; /** * 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 /** * Default depth in spec is infinite. Limit depth to 3 by default as * infinite depth makes operations very expensive. */ public static int maxDepth = 3; /** * Is access allowed via WebDAV to the special paths (/WEB-INF and * /META-INF)? */ private boolean allowSpecialPaths = false; @Autowired private VFSLockManagerImpl lockManager; @Autowired private WebDAVManager webDAVManager; @Autowired private WebDAVModule webDAVModule; public WebDAVDispatcherImpl() { // } @Override protected WebResourceRoot getResources(HttpServletRequest req) { return webDAVManager.getWebDAVRoot(req); } /** * Return JAXP document builder instance. */ protected DocumentBuilder getDocumentBuilder(HttpServletRequest req) 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(req.getServletContext())); } catch(ParserConfigurationException e) { throw new ServletException("webdavservlet.jaxpfailed"); } return documentBuilder; } @Override public void execute(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (webDAVManager == null) { resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } else if(webDAVModule == null || !webDAVModule.isEnabled()) { resp.sendError(WebdavStatus.SC_FORBIDDEN); } else if (webDAVManager.handleAuthentication(req, resp)) { webdavService(req, resp); } else { //the method handleAuthentication will send the challenges for authentication } } /** * Handles the special WebDAV methods. */ private void webdavService(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { final String path = getRelativePath(req); // 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 (log.isDebug()) { log.debug("[" + 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 if (method.equals(METHOD_GET)) { doGet(req, resp); } else if (method.equals(METHOD_HEAD)) { doHead(req, resp); } else if (method.equals(METHOD_POST)) { doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(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; } /** * Override the DefaultServlet implementation and only use the PathInfo. If * the ServletPath is non-null, it will be because the WebDAV servlet has * been mapped to a url other than /* to configure editing at different url * than normal viewing. * * @param request The servlet request we are processing */ @Override protected String getRelativePath(HttpServletRequest request) { // Are we being processed by a RequestDispatcher.include()? if (request.getAttribute( RequestDispatcher.INCLUDE_REQUEST_URI) != null) { String result = (String) request.getAttribute( RequestDispatcher.INCLUDE_PATH_INFO); if ((result == null) || (result.equals(""))) result = "/"; return (result); } // No, extract the desired path directly from the request String result = request.getPathInfo(); if ((result == null) || (result.equals(""))) { result = "/"; } result = Normalizer.normalize(result, Normalizer.Form.NFC); return (result); } /** * 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 request * @param resp The response * @throws ServletException If an error occurs * @throws IOException If an IO error occurs */ public void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.addHeader("DAV", "1,2"); StringBuilder methodsAllowed = determineMethodsAllowed(req); resp.addHeader("Allow", methodsAllowed.toString()); resp.addHeader("MS-Author-Via", "DAV"); } @Override public void doRootOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.addHeader("DAV", "1,2"); resp.setHeader("Allow", "OPTIONS, GET, HEAD, POST, DELETE, TRACE, PROPPATCH, COPY, MOVE, LOCK, UNLOCK"); resp.addHeader("MS-Author-Via", "DAV"); resp.setDateHeader("Date", new Date().getTime()); resp.setHeader("Content-Length", "0"); resp.setContentLength(0); resp.setStatus(200); } @Override public void doWebdavOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.addHeader("DAV", "1,2"); resp.setHeader("Allow", "PROPFIND, OPTIONS"); resp.addHeader("MS-Author-Via", "DAV"); resp.setDateHeader("Date", new Date().getTime()); resp.setHeader("Content-Length", "0"); resp.setContentLength(0); resp.setStatus(200); } /** * PROPFIND Method. */ public void doPropfind(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 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.getContentLength() > 0) { DocumentBuilder documentBuilder = getDocumentBuilder(req); 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 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; } } } WebResourceRoot resources = getResources(req); WebResource resource = resources.getResource(path); if (!resource.exists()) { int slash = path.lastIndexOf('/'); if (slash != -1) { String parentPath = path.substring(0, slash); WebResource parentResource = resources.getResource(parentPath); Vector<String> currentLockNullResources = lockManager.getLockNullResource(parentResource); 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<String>(); stack.push(path); // Stack of the objects one level below Stack<String> stackBelow = new Stack<String>(); while ((!stack.isEmpty()) && (depth >= 0)) { final String currentPath = stack.pop(); parseProperties(req, generatedXML, currentPath, type, properties); resource = resources.getResource(currentPath); if (resource.isDirectory() && (depth > 0)) { Collection<VFSItem> entries = resources.list(currentPath); for (VFSItem entry : entries) { String newPath = currentPath; if (!(newPath.endsWith("/"))) newPath += "/"; newPath += entry.getName(); 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 = lockManager.getLockNullResource(resource); 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<String>(); } generatedXML.sendData(); } } generatedXML.writeElement("D", "multistatus", XMLWriter.CLOSING); generatedXML.sendData(); } /** * PROPPATCH Method. */ public void doProppatch(HttpServletRequest req, HttpServletResponse resp) throws IOException { if (isLocked(req)) { resp.sendError(WebdavStatus.SC_LOCKED); return; } String path = getRelativePath(req); WebResourceRoot resources = getResources(req); if(!resources.canWrite(path)) { resp.sendError(WebdavStatus.SC_FORBIDDEN); return; } if (path.endsWith("/")) { path = path.substring(0, path.length() - 1); } resp.setStatus(WebdavStatus.SC_MULTI_STATUS); resp.setContentType("text/xml; charset=UTF-8"); /* <?xml version="1.0" encoding="utf-8" ?> <D:multistatus xmlns:D="DAV:" xmlns:Z="http://www.w3.com/standards/z39.50"> <D:response> <D:href>http://www.foo.com/bar.html</D:href> <D:propstat> <D:prop><Z:Authors/></D:prop> <D:status>HTTP/1.1 424 Failed Dependency</D:status> </D:propstat> <D:propstat> <D:prop><Z:Copyright-Owner/></D:prop> <D:status>HTTP/1.1 409 Conflict</D:status> </D:propstat> <D:responsedescription> Copyright Owner can not be deleted or altered.</D:responsedescription> </D:response> </D:multistatus> */ XMLWriter generatedXML = new XMLWriter(resp.getWriter()); generatedXML.writeXMLHeader(); generatedXML.writeElement("D", DEFAULT_NAMESPACE, "multistatus", XMLWriter.OPENING); parseProperties( req, generatedXML, path, 32, new Vector<String>()); generatedXML.writeElement("D", "multistatus", XMLWriter.CLOSING); generatedXML.sendData(); } /** * MKCOL Method. */ public void doMkcol(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (isLocked(req)) { resp.sendError(WebdavStatus.SC_LOCKED); return; } final String path = getRelativePath(req); final WebResourceRoot resources = getResources(req); final WebResource resource = resources.getResource(path); // Can't create a collection if a resource already exists at the given // path if (resource.exists()) { // Get allowed methods StringBuilder methodsAllowed = determineMethodsAllowed(req); resp.addHeader("Allow", methodsAllowed.toString()); resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED); return; } if (req.getContentLength() > 0) { DocumentBuilder documentBuilder = getDocumentBuilder(req); 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 lockManager.removeLockNullResource(resource); } else { resp.sendError(WebdavStatus.SC_CONFLICT, WebdavStatus.getStatusText (WebdavStatus.SC_CONFLICT)); } } /** * DELETE Method. */ public void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (isLocked(req)) { resp.sendError(WebdavStatus.SC_LOCKED); return; } String path = this.getRelativePath(req); WebResourceRoot resources = this.getResources(req); if(!resources.canDelete(path)) { resp.sendError(WebdavStatus.SC_FORBIDDEN); return; } deleteResource(req, resp); } /** * Process a HEAD request for the specified resource. * * @param request The servlet request we are processing * @param response The servlet response we are creating * * @exception IOException if an input/output error occurs * @exception ServletException if a servlet-specified error occurs */ protected void doHead(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // Serve the requested resource, without the data content boolean serveContent = DispatcherType.INCLUDE.equals(request.getDispatcherType()); serveResource(request, response, serveContent, fileEncoding); } public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { serveResource(request, response, true, fileEncoding); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { serveResource(request, response, true, fileEncoding); } /** * 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 */ public void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (isLocked(req)) { resp.sendError(WebdavStatus.SC_LOCKED); return; } final String path = getRelativePath(req); final WebResourceRoot resources = getResources(req); if (!resources.canWrite(path)) { resp.sendError(WebdavStatus.SC_FORBIDDEN); return; } final WebResource resource = resources.getResource(path); Range range = parseContentRange(req, resp); InputStream resourceInputStream = null; try { // Append data specified in ranges to existing content for this // resource - create a temp. file on the local filesystem to // perform this operation // Assume just one range is specified for now if (range != null) { File contentFile = executePartialPut(req, range, path); resourceInputStream = new FileInputStream(contentFile); } else { resourceInputStream = req.getInputStream(); } if (resources.write(path, resourceInputStream, true, null)) { if (resource.exists()) { resp.setStatus(HttpServletResponse.SC_NO_CONTENT); } else { resp.setStatus(HttpServletResponse.SC_CREATED); PrintWriter writer = resp.getWriter(); writer.append("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n") .append("<html><head>\n") .append("<title>201 Created</title>\n") .append("</head><body>\n") .append("<h1>Created</h1>\n") .append("<p>Resource ").append(path).append(" created.</p>\n") .append("</body></html>\n"); resp.setContentType("text/html; charset=ISO-8859-1"); String location = Settings.getServerContextPathURI() + path; resp.setHeader("Location", location); } } else { resp.sendError(HttpServletResponse.SC_CONFLICT); } } catch(QuotaExceededException e) { resp.sendError(HttpServletResponse.SC_CONFLICT); } finally { if (resourceInputStream != null) { try { resourceInputStream.close(); } catch (IOException ioe) { // Ignore } } } // Removing any lock-null resource which would be present lockManager.removeLockNullResource(resource); } /** * Handle a partial PUT. New content specified in request is appended to * existing content in oldRevisionContent (if present). This code does * not support simultaneous partial updates to the same resource. */ private File executePartialPut(HttpServletRequest req, Range range, String path) throws IOException { // Append data specified in ranges to existing content for this // resource - create a temp. file on the local filesystem to // perform this operation File tempDir = (File) req.getServletContext().getAttribute(ServletContext.TEMPDIR); // Convert all '/' characters to '.' in resourcePath String convertedResourcePath = path.replace('/', '.'); File contentFile = new File(tempDir, convertedResourcePath); if (contentFile.createNewFile()) { // Clean up contentFile when Tomcat is terminated contentFile.deleteOnExit(); } RandomAccessFile randAccessContentFile = new RandomAccessFile(contentFile, "rw"); WebResource oldResource = getResources(req).getResource(path); // Copy data in oldRevisionContent to contentFile if (oldResource.isFile()) { BufferedInputStream bufOldRevStream = new BufferedInputStream(oldResource.getInputStream(), BUFFER_SIZE); int numBytesRead; byte[] copyBuffer = new byte[BUFFER_SIZE]; while ((numBytesRead = bufOldRevStream.read(copyBuffer)) != -1) { randAccessContentFile.write(copyBuffer, 0, numBytesRead); } bufOldRevStream.close(); } randAccessContentFile.setLength(range.length); // Append data in request input stream to contentFile randAccessContentFile.seek(range.start); int numBytesRead; byte[] transferBuffer = new byte[BUFFER_SIZE]; BufferedInputStream requestBufInStream = new BufferedInputStream(req.getInputStream(), BUFFER_SIZE); while ((numBytesRead = requestBufInStream.read(transferBuffer)) != -1) { randAccessContentFile.write(transferBuffer, 0, numBytesRead); } randAccessContentFile.close(); requestBufInStream.close(); return contentFile; } /** * COPY Method. */ public void doCopy(HttpServletRequest req, HttpServletResponse resp) throws IOException { String path = getRelativePath(req); WebResourceRoot resources = getResources(req); if (resources.canWrite(path)) { copyResource(req, resp, false); } else { resp.sendError(WebdavStatus.SC_FORBIDDEN); } } /** * MOVE Method. */ public void doMove(HttpServletRequest req, HttpServletResponse resp) throws IOException { if (isLocked(req)) { resp.sendError(WebdavStatus.SC_LOCKED); return; } String path = getRelativePath(req); WebResourceRoot resources = this.getResources(req); if(!resources.canRename(path)) { resp.sendError(WebdavStatus.SC_FORBIDDEN); return; } if (copyResource(req, resp, true)) { deleteResource(path, req, resp, false); } } /** * LOCK Method. */ public void doLock(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if(isLocked(req)) { resp.sendError(WebdavStatus.SC_LOCKED); return; } final String path = getRelativePath(req); final WebResourceRoot resources = getResources(req); if(!resources.canWrite(path)) { resp.sendError(WebdavStatus.SC_FORBIDDEN); return; } UserSession usess = webDAVManager.getUserSession(req); LockInfo lock = new LockInfo(usess.getIdentity().getKey(), true, false); // Parsing lock request // Parsing depth header String depthStr = req.getHeader("Depth"); if (depthStr == null) { lock.setDepth(maxDepth); } else { if (depthStr.equals("0")) { lock.setDepth(0); } else { lock.setDepth(maxDepth); } } if(log.isDebug()) { log.debug("Lock the ressource: " + path + " with depth:" + lock.getDepth()); } // 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 = (new Integer(lockDurationStr.substring(7))).intValue(); } else { if (lockDurationStr.equalsIgnoreCase("infinity")) { lockDuration = MAX_TIMEOUT; } else { try { lockDuration = (new Integer(lockDurationStr)).intValue(); } catch (NumberFormatException e) { lockDuration = MAX_TIMEOUT; } } } if (lockDuration == 0) { lockDuration = DEFAULT_TIMEOUT; } if (lockDuration > MAX_TIMEOUT) { lockDuration = MAX_TIMEOUT; } } lock.setExpiresAt(System.currentTimeMillis() + (lockDuration * 1000)); int lockRequestType = LOCK_CREATION; Node lockInfoNode = null; DocumentBuilder documentBuilder = getDocumentBuilder(req); 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.setScope(tempScope.substring(tempScope.indexOf(':') + 1)); } else { lock.setScope(tempScope); } break; } } if (lock.getScope() == 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.setType(tempType.substring(tempType.indexOf(':') + 1)); } else { lock.setType(tempType); } break; } } if (lock.getType() == 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.setOwner(lock.getOwner() + currentNode.getNodeValue()); break; case Node.ELEMENT_NODE: strWriter = new StringWriter(); domWriter = new DOMWriter(strWriter, true); domWriter.print(currentNode); lock.setOwner(lock.getOwner() + strWriter.toString()); break; } } if (lock.getOwner() == null) { // Bad request resp.setStatus(WebdavStatus.SC_BAD_REQUEST); } } else { lock.setOwner(""); } } final WebResource resource = resources.getResource(path); lock.setWebResource(resource); Iterator<LockInfo> locksList = null; if (lockRequestType == LOCK_CREATION) { // Generating lock id String lockToken = lockManager.generateLockToken(lock, usess.getIdentity().getKey()); if (resource.isDirectory() && lock.getDepth() == 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<String>(); locksList = lockManager.getCollectionLocks(); while (locksList.hasNext()) { LockInfo currentLock = locksList.next(); if (currentLock.hasExpired()) { WebResource currentLockedResource = resources.getResource(currentLock.getWebPath()); lockManager.removeResourceLock(currentLockedResource); continue; } if ( (currentLock.getWebPath().startsWith(lock.getWebPath())) && ((currentLock.isExclusive()) || (lock.isExclusive())) ) { // A child collection of this collection is locked lockPaths.addElement(currentLock.getWebPath()); } } locksList = lockManager.getResourceLocks(); while (locksList.hasNext()) { LockInfo currentLock = locksList.next(); if (currentLock.hasExpired()) { WebResource currentLockedResource = resources.getResource(currentLock.getWebPath()); lockManager.removeResourceLock(currentLockedResource); continue; } if ( (currentLock.getWebPath().startsWith(lock.getWebPath())) && ((currentLock.isExclusive()) || (lock.isExclusive())) ) { // A child resource of this collection is locked lockPaths.addElement(currentLock.getWebPath()); } } 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 = lockManager.getCollectionLocks(); while (locksList.hasNext()) { LockInfo currentLock = locksList.next(); if (currentLock.getWebPath().equals(lock.getWebPath())) { if (currentLock.isExclusive()) { resp.sendError(WebdavStatus.SC_LOCKED); return; } else { if (lock.isExclusive()) { resp.sendError(WebdavStatus.SC_LOCKED); return; } } currentLock.addToken(lockToken); lock = currentLock; addLock = false; } } if (addLock) { lock.addToken(lockToken); lockManager.addCollectionLock(lock); } } else { // Locking a single resource // Retrieving an already existing lock on that resource WebResource lockedResource = resources.getResource(lock.getWebPath()); LockInfo presentLock = lockManager.getResourceLock(lockedResource); 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.setWebDAVLock(true); presentLock.addToken(lockToken); lock = presentLock; } } else { lock.addToken(lockToken); lockManager.putResourceLock(lockedResource, lock); // Checking if a resource exists at this path if (!resource.exists()) { // "Creating" a lock-null resource int slash = lock.getWebPath().lastIndexOf('/'); String parentPath = lock.getWebPath().substring(0, slash); WebResource parentResource = resources.getResource(parentPath); Vector<String> lockNulls = lockManager.getLockNullResource(parentResource); if (lockNulls == null) { lockNulls = new Vector<String>(); lockManager.putLockNullResource(parentPath, lockNulls); } lockNulls.addElement(lock.getWebPath()); } // 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 = lockManager.getResourceLock(resource); if (toRenew != null) { // At least one of the tokens of the locks must have been given Iterator<String> tokenList = toRenew.tokens(); while (tokenList.hasNext()) { String token = tokenList.next(); if (ifHeader.indexOf(token) != -1) { toRenew.setExpiresAt(lock.getExpiresAt()); toRenew.setWebDAVLock(true); lock = toRenew; } } } // Checking inheritable collection locks Iterator<LockInfo> collectionLocksList = lockManager.getCollectionLocks(); while (collectionLocksList.hasNext()) { toRenew = collectionLocksList.next(); if (path.equals(toRenew.getWebPath())) { Iterator<String> tokenList = toRenew.tokens(); while (tokenList.hasNext()) { String token = tokenList.next(); if (ifHeader.indexOf(token) != -1) { toRenew.setExpiresAt(lock.getExpiresAt()); 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. */ protected void doUnlock(HttpServletRequest req, HttpServletResponse resp) throws IOException { if (isLocked(req)) { resp.sendError(WebdavStatus.SC_LOCKED); return; } final String path = getRelativePath(req); final WebResourceRoot resources = getResources(req); final WebResource resource = resources.getResource(path); String lockTokenHeader = req.getHeader("Lock-Token"); if (lockTokenHeader == null) { lockTokenHeader = ""; } else if(lockTokenHeader != null && lockTokenHeader.startsWith("<opaquelocktoken") && !lockTokenHeader.endsWith(">")) { lockTokenHeader += ">"; } // Checking resource locks if(log.isDebug()) { log.debug("Unlock the ressource: " + path); } LockInfo lock = lockManager.getResourceLock(resource); if (lock != null) { // At least one of the tokens of the locks must have been given Iterator<String> tokenList = lock.tokens(); while (tokenList.hasNext()) { String token = tokenList.next(); if (lockTokenHeader.indexOf(token) != -1) { lock.removeToken(token); } } if (lock.getTokensSize() == 0) { lockManager.removeResourceLock(resource); // Removing any lock-null resource which would be present lockManager.removeLockNullResource(resource); } } // Checking inheritable collection locks Iterator<LockInfo> collectionLocksList = lockManager.getCollectionLocks(); while (collectionLocksList.hasNext()) { lock = collectionLocksList.next(); if (path.equals(lock.getWebPath())) { Iterator<String> tokenList = lock.tokens(); while (tokenList.hasNext()) { String token = tokenList.next(); if (lockTokenHeader.indexOf(token) != -1) { lock.removeToken(token); break; } } if (lock.getTokensSize() == 0) { lockManager.removeCollectionLock(lock); // Removing any lock-null resource which would be present lockManager.removeLockNullResource(resource); } } } 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 boolean true if the resource is locked (and no appropriate * lock token has been found for at least one of the non-shared locks which * are present on the resource). */ private boolean isLocked(HttpServletRequest req) { final String path = getRelativePath(req); final WebResourceRoot resources = getResources(req); final WebResource resource = resources.getResource(path); String ifHeader = req.getHeader("If"); if (ifHeader == null) ifHeader = ""; String lockTokenHeader = req.getHeader("Lock-Token"); if (lockTokenHeader == null) { lockTokenHeader = ""; } else if(lockTokenHeader != null && lockTokenHeader.startsWith("<opaquelocktoken") && !lockTokenHeader.endsWith(">")) { lockTokenHeader += ">"; } UserSession usess = webDAVManager.getUserSession(req); boolean locked = lockManager.isLocked(resource, ifHeader + lockTokenHeader, usess.getIdentity()); if(locked && log.isDebug()) { log.debug("Ressource is locked: " + req.getPathInfo()); } return locked; } /** * Copy a resource. * * @param req Servlet request * @param resp Servlet response * @return boolean true if the copy is successful */ private boolean copyResource(HttpServletRequest req, HttpServletResponse resp, boolean moved) throws IOException { // Parsing destination header String destinationPath = getDestinationPath(req); if (destinationPath == null) { resp.sendError(WebdavStatus.SC_BAD_REQUEST); return false; } String pathInfo = req.getPathInfo(); if (pathInfo != null) { String servletPath = req.getServletPath(); if ((servletPath != null) && (destinationPath.startsWith(servletPath))) { destinationPath = destinationPath .substring(servletPath.length()); } } if (log.isDebug()) log.debug("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 final WebResourceRoot resources = getResources(req); final 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(req, errorList, path, destinationPath, moved); 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 lockManager.removeLockNullResource(destination); return true; } private String getDestinationPath(HttpServletRequest req) { String destinationPath = req.getHeader("Destination"); if (destinationPath == null) { return null; } // Remove url encoding from destination destinationPath = RequestUtil.URLDecode(destinationPath, "UTF8"); int protocolIndex = destinationPath.indexOf("://"); if (protocolIndex >= 0) { // if the Destination URL contains the protocol, we can safely // trim everything upto the first "/" character after "://" int firstSeparator = destinationPath.indexOf("/", protocolIndex + 4); if (firstSeparator < 0) { destinationPath = "/"; } else { destinationPath = destinationPath.substring(firstSeparator); } } else { String hostName = req.getServerName(); if ((hostName != null) && (destinationPath.startsWith(hostName))) { destinationPath = destinationPath.substring(hostName.length()); } int portIndex = destinationPath.indexOf(":"); if (portIndex >= 0) { destinationPath = destinationPath.substring(portIndex); } if (destinationPath.startsWith(":")) { int firstSeparator = destinationPath.indexOf("/"); if (firstSeparator < 0) { destinationPath = "/"; } else { destinationPath = destinationPath.substring(firstSeparator); } } } // Normalise destination path (remove '.' and '..') destinationPath = RequestUtil.normalize(destinationPath); String contextPath = req.getContextPath(); if ((contextPath != null) && (destinationPath.startsWith(contextPath))) { destinationPath = destinationPath.substring(contextPath.length()); } return destinationPath; } /** * 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 */ private boolean copyResource(HttpServletRequest req, Hashtable<String,Integer> errorList, String source, String dest, boolean moved) { if (log.isDebug()) log.debug("Copy: " + source + " To: " + dest); WebResourceRoot resources = getResources(req); WebResource sourceResource = resources.getResource(source); if (sourceResource.isDirectory()) { if (!resources.mkdir(dest)) { WebResource destResource = resources.getResource(dest); if (!destResource.isDirectory()) { errorList.put(dest, new Integer(WebdavStatus.SC_CONFLICT)); return false; } } Collection<VFSItem> entries = resources.list(source); for (VFSItem entry : entries) { String childDest = dest; if (!childDest.equals("/")) { childDest += "/"; } childDest += entry.getName(); String childSrc = source; if (!childSrc.equals("/")) { childSrc += "/"; } childSrc += entry.getName(); copyResource(req, errorList, childSrc, childDest, moved); } } else if (sourceResource.isFile()) { WebResource destResource = resources.getResource(dest); if (!destResource.exists() && !destResource.getPath().endsWith("/")) { int lastSlash = destResource.getPath().lastIndexOf('/'); if (lastSlash > 0) { String parent = destResource.getPath().substring(0, lastSlash); WebResource parentResource = resources.getResource(parent); if (!parentResource.isDirectory()) { errorList.put(source, new Integer(WebdavStatus.SC_CONFLICT)); return false; } } } WebResource movedFrom = moved ? sourceResource : null; try { if (!resources.write(dest, sourceResource.getInputStream(), false, movedFrom)) { errorList.put(source, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR)); return false; } } catch (QuotaExceededException e) { errorList.put(source, new Integer(WebdavStatus.SC_INSUFFICIENT_SPACE_ON_RESOURCE)); return false; } } else { errorList.put(source, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR)); return false; } return true; } /** * Delete a resource. * * @param req Servlet request * @param resp Servlet response * @return boolean true if the copy is successful */ private boolean deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException { String path = getRelativePath(req); return deleteResource(path, req, resp, true); } /** * Delete a resource. * * @param path Path of the resource which is to be deleted * @param req Servlet request * @param resp Servlet response * @param setStatus Should the response status be set on successful * completion */ private boolean deleteResource(final 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 = ""; final WebResourceRoot resources = getResources(req); final WebResource resource = resources.getResource(path); UserSession usess = webDAVManager.getUserSession(req); if (lockManager.isLocked(resource, ifHeader + lockTokenHeader, usess.getIdentity())) { resp.sendError(WebdavStatus.SC_LOCKED); return false; } if (!resource.exists()) { resp.sendError(WebdavStatus.SC_NOT_FOUND); return false; } if (!resource.isDirectory()) { if (!resources.delete(resource)) { resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); return false; } } else { Hashtable<String,Integer> errorList = new Hashtable<String,Integer>(); deleteCollection(req, path, errorList); if (!resources.delete(resource)) { errorList.put(path, new Integer (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 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, Map<String,Integer> errorList) { if (log.isDebug()) log.debug("Delete:" + path); // Prevent deletion of special subdirectories if (isSpecialPath(path)) { errorList.put(path, new Integer(WebdavStatus.SC_FORBIDDEN)); return; } String ifHeader = req.getHeader("If"); if (ifHeader == null) ifHeader = ""; String lockTokenHeader = req.getHeader("Lock-Token"); if (lockTokenHeader == null) lockTokenHeader = ""; final WebResourceRoot resources = getResources(req); Collection<VFSItem> entries = resources.list(path); UserSession usess = webDAVManager.getUserSession(req); for (VFSItem entry : entries) { String childName = path; if (!childName.equals("/")) { childName += "/"; } childName += entry.getName(); WebResource childResource = resources.getResource(childName); if (lockManager.isLocked(childResource, ifHeader + lockTokenHeader, usess.getIdentity())) { errorList.put(childName, new Integer(WebdavStatus.SC_LOCKED)); } else { if (childResource.isDirectory()) { deleteCollection(req, childName, errorList); } if (!resources.delete(childResource)) { if (!childResource.isDirectory()) { // If it's not a collection, then it's an unknown error errorList.put(childName, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR)); } } } } } /** * Send a multistatus element containing a complete error report to the * client. * * @param req Servlet request * @param resp Servlet response * @param errorList List of error to be displayed */ private void sendReport(HttpServletRequest req, HttpServletResponse resp, Hashtable<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 resources Resources object associated with this context * @param generatedXML XML response to the Propfind request * @param path Path of the current resource * @param type Propfind type * @param propertiesVector If the propfind type is find properties by * name, then this Vector contains those properties */ private void parseProperties(HttpServletRequest req, XMLWriter generatedXML, final String path, int type, Vector<String> propertiesVector) { // Exclude any resource in the /WEB-INF and /META-INF subdirectories if (isSpecialPath(path)) return; final WebResourceRoot resources = getResources(req); final 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; } 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); 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 nfcNormalizedHref = Normalizer.normalize(href, Normalizer.Form.NFC); generatedXML.writeText(rewriteUrl(nfcNormalizedHref)); generatedXML.writeElement("D", "href", XMLWriter.CLOSING); String resourceName = Normalizer.normalize(path, Normalizer.Form.NFC); int lastSlash = path.lastIndexOf('/'); if (lastSlash != -1) resourceName = resourceName.substring(lastSlash + 1); switch (type) { case FIND_ALL_PROP : generatedXML.writeElement("D", "propstat", XMLWriter.OPENING); generatedXML.writeElement("D", "prop", XMLWriter.OPENING); generatedXML.writeProperty("D", "creationdate", getISOCreationDate(resource.getCreation())); generatedXML.writeElement("D", "displayname", XMLWriter.OPENING); generatedXML.writeData(resourceName); generatedXML.writeElement("D", "displayname", XMLWriter.CLOSING); if (resource.isFile()) { generatedXML.writeProperty("D", "getlastmodified", FastHttpDateFormat.formatDate(resource.getLastModified(), null)); generatedXML.writeProperty("D", "getcontentlength",String.valueOf(resource.getContentLength())); String contentType = req.getServletContext().getMimeType(resource.getName()); if (contentType != null) { generatedXML.writeProperty("D", "getcontenttype",contentType); } generatedXML.writeProperty("D", "getetag",resource.getETag()); 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(resource, 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 (resource.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(resource.getCreation())); } 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 (resource.isDirectory()) { propertiesNotFound.addElement(property); } else { generatedXML.writeElement("D", "getcontentlanguage", XMLWriter.NO_CONTENT); } } else if (property.equals("getcontentlength")) { if (resource.isDirectory()) { propertiesNotFound.addElement(property); } else { generatedXML.writeProperty ("D", "getcontentlength", (String.valueOf(resource.getContentLength()))); } } else if (property.equals("getcontenttype")) { if (resource.isDirectory()) { propertiesNotFound.addElement(property); } else { generatedXML.writeProperty ("D", "getcontenttype", req.getServletContext().getMimeType (resource.getName())); } } else if (property.equals("getetag")) { if (resource.isDirectory()) { propertiesNotFound.addElement(property); } else { generatedXML.writeProperty ("D", "getetag", resource.getETag()); } } else if (property.equals("getlastmodified")) { if (resource.isDirectory()) { propertiesNotFound.addElement(property); } else { generatedXML.writeProperty ("D", "getlastmodified", FastHttpDateFormat.formatDate (resource.getLastModified(), null)); } } else if (property.equals("resourcetype")) { if (resource.isDirectory()) { generatedXML.writeElement("D", "resourcetype", XMLWriter.OPENING); generatedXML.writeElement("D", "collection", XMLWriter.NO_CONTENT); generatedXML.writeElement("D", "resourcetype", XMLWriter.CLOSING); } else { generatedXML.writeElement("D", "resourcetype", XMLWriter.NO_CONTENT); } } 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(resource, 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); } /** * Propfind helper method. Displays the properties of a lock-null resource. * * @param resources Resources object associated with this context * @param generatedXML XML response to the Propfind request * @param path Path of the current resource * @param type Propfind type * @param propertiesVector If the propfind type is find properties by * name, then this Vector contains those properties */ private void parseLockNullProperties(HttpServletRequest req, XMLWriter generatedXML, final String path, int type, Vector<String> propertiesVector) { // Exclude any resource in the /WEB-INF and /META-INF subdirectories if (isSpecialPath(path)) return; final WebResourceRoot resources = getResources(req); final WebResource resource = resources.getResource(path); // Retrieving the lock associated with the lock-null resource LockInfo lock = lockManager.getResourceLock(resource); if (lock == null) return; 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); String absoluteUri = req.getRequestURI(); String relativePath = getRelativePath(req); String toAppend = path.substring(relativePath.length()); if (!toAppend.startsWith("/")) toAppend = "/" + toAppend; String normalizedUrl = RequestUtil.normalize(absoluteUri + toAppend); String nfcNormalizedUrl = Normalizer.normalize(normalizedUrl, Normalizer.Form.NFC); generatedXML.writeText(rewriteUrl(nfcNormalizedUrl)); generatedXML.writeElement("D", "href", XMLWriter.CLOSING); String resourceName = Normalizer.normalize(path, Normalizer.Form.NFC); int lastSlash = path.lastIndexOf('/'); if (lastSlash != -1) { resourceName = resourceName.substring(lastSlash + 1); } switch (type) { case FIND_ALL_PROP : generatedXML.writeElement("D", "propstat", XMLWriter.OPENING); generatedXML.writeElement("D", "prop", XMLWriter.OPENING); generatedXML.writeProperty("D", "creationdate", getISOCreationDate(lock.getCreationDate().getTime())); generatedXML.writeElement("D", "displayname", XMLWriter.OPENING); generatedXML.writeData(resourceName); generatedXML.writeElement("D", "displayname", XMLWriter.CLOSING); generatedXML.writeProperty("D", "getlastmodified", FastHttpDateFormat.formatDate(lock.getCreationDate().getTime(), null)); generatedXML.writeProperty("D", "getcontentlength", String.valueOf(0)); generatedXML.writeProperty("D", "getcontenttype", ""); generatedXML.writeProperty("D", "getetag", ""); generatedXML.writeElement("D", "resourcetype", XMLWriter.OPENING); generatedXML.writeElement("D", "lock-null", 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(resource, 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); 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(lock.getCreationDate().getTime())); } 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")) { generatedXML.writeElement("D", "getcontentlanguage", XMLWriter.NO_CONTENT); } else if (property.equals("getcontentlength")) { generatedXML.writeProperty("D", "getcontentlength", (String.valueOf(0))); } else if (property.equals("getcontenttype")) { generatedXML.writeProperty("D", "getcontenttype", ""); } else if (property.equals("getetag")) { generatedXML.writeProperty("D", "getetag", ""); } else if (property.equals("getlastmodified")) { generatedXML.writeProperty("D", "getlastmodified", FastHttpDateFormat.formatDate(lock.getCreationDate().getTime(), null)); } else if (property.equals("resourcetype")) { generatedXML.writeElement("D", "resourcetype", XMLWriter.OPENING); generatedXML.writeElement("D", "lock-null", 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(resource, 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 true if at least one lock was displayed */ private boolean generateLockDiscovery(final WebResource resource, final String path, XMLWriter generatedXML) { LockInfo resourceLock = lockManager.getResourceLock(resource); Iterator<LockInfo> collectionLocksList = lockManager.getCollectionLocks(); boolean wroteStart = false; if (resourceLock != null) { wroteStart = true; generatedXML.writeElement("D", "lockdiscovery", XMLWriter.OPENING); resourceLock.toXML(generatedXML); } else { LockInfo ooLock = lockManager.getVFSLock(resource); if(ooLock != null) { wroteStart = true; generatedXML.writeElement("D", "lockdiscovery", XMLWriter.OPENING); ooLock.toXML(generatedXML); } } while (collectionLocksList.hasNext()) { LockInfo currentLock = collectionLocksList.next(); if (path.startsWith(currentLock.getWebPath())) { 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. */ private String getISOCreationDate(long creationDate) { return creationDateFormat.format(new Date(creationDate)); } /** * Determines the methods normally allowed for the resource. * */ private StringBuilder determineMethodsAllowed(HttpServletRequest req) { StringBuilder methodsAllowed = new StringBuilder(); WebResourceRoot resources = getResources(req); WebResource resource = resources.getResource(getRelativePath(req)); if (!resource.exists()) { methodsAllowed.append("OPTIONS, MKCOL, PUT, LOCK"); return methodsAllowed; } methodsAllowed.append("OPTIONS, GET, HEAD, POST, DELETE, TRACE"); methodsAllowed.append(", PROPPATCH, COPY, MOVE, LOCK, UNLOCK"); methodsAllowed.append(", PROPFIND"); if (resource.isFile()) { methodsAllowed.append(", PUT"); } return methodsAllowed; } // -------------------------------------------------- LockInfo Inner Class // --------------------------------------------- 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("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); } }