Java tutorial
package org.webdavaccess.servlet; /* * Copyright 1999,2004 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.io.IOException; import java.io.OutputStream; import java.io.InputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Writer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; import java.util.Locale; import java.util.Properties; import java.util.TimeZone; import java.util.Vector; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.catalina.util.MD5Encoder; import org.apache.catalina.util.RequestUtil; import org.apache.catalina.util.URLEncoder; import org.apache.catalina.util.XMLWriter; import org.apache.commons.httpclient.auth.AuthenticationException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.webdav.lib.util.WebdavStatus; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.webdavaccess.IWebdavAlias; import org.webdavaccess.IWebdavAuthorization; import org.webdavaccess.IWebdavStorage; import org.webdavaccess.ResourceLocks; import org.webdavaccess.WebdavAliasFactory; import org.webdavaccess.WebdavAuthorizationFactory; import org.webdavaccess.WebdavStoreFactory; import org.webdavaccess.exceptions.AccessDeniedException; import org.webdavaccess.exceptions.ObjectAlreadyExistsException; import org.webdavaccess.exceptions.ObjectNotFoundException; import org.webdavaccess.exceptions.UnauthenticatedException; import org.webdavaccess.exceptions.WebdavException; import org.xml.sax.InputSource; /** * Servlet which provides support for WebDAV level 2. * * the original class is org.apache.catalina.servlets.WebdavServlet by Remy * Maucherat, which was heavily changed * * @author Remy Maucherat * @author Lubos Pochman * * Original class org.apache.catalina.servlets.WebdavServlet modified by Remy Maucherat * */ public class WebdavServlet extends HttpServlet { private static final long serialVersionUID = -3879804749319022302L; private static Log log = LogFactory.getLog(WebdavServlet.class); // -------------------------------------------------------------- Constants public static final String WEBDAV_AUTHENTICATION_IMPLEMENTATION = "webdavAuthorizationImplementation"; public static final String WEBDAV_ALIAS_IMPLEMENTATION = "webdavAliasManagerImplementation"; public static final String WEBDAV_RESOURCE_IMPLEMENTATION = "webdavStoreImplementation"; private static final String METHOD_HEAD = "HEAD"; 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_PUT = "PUT"; private static final String METHOD_GET = "GET"; private static final String METHOD_OPTIONS = "OPTIONS"; private static final String METHOD_DELETE = "DELETE"; // header names public static final String TOMCAT_CSS = "H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} " + "H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} " + "H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} " + "BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} " + "B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} " + "P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}" + "A {color : black;}" + "A.name {color : black;}" + "HR {color : #525D76;}"; /** * MD5 message digest provider. */ protected static MessageDigest md5Helper; /** * The MD5 helper object for this class. */ protected static final MD5Encoder md5Encoder = new MD5Encoder(); /** * Default depth is infite. */ private static final int INFINITY = 3; // To limit tree browsing a bit /** * PROPFIND - Specify a property mask. */ private static final int FIND_BY_PROPERTY = 0; /** * PROPFIND - Display all properties. */ private static final int FIND_ALL_PROP = 1; /** * PROPFIND - Return property names. */ private static final int FIND_PROPERTY_NAMES = 2; /** * size of the io-buffer */ private static int BUF_SIZE = 50000; /** * Default namespace. */ protected static final String DEFAULT_NAMESPACE = "DAV:"; /** * Simple date format for the creation date ISO representation (partial). */ protected static final SimpleDateFormat creationDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); /** * indicates that the store is readonly ? */ private static final String READONLY_PARAMETER = "readonly"; private boolean readOnly; public static final String DEBUG_PARAMETER = "servletDebug"; static { creationDateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); } /** * Array containing the safe characters set. */ protected static URLEncoder urlEncoder; /** * GMT timezone - all HTTP dates are on GMT */ static { urlEncoder = new URLEncoder(); urlEncoder.addSafeCharacter('-'); urlEncoder.addSafeCharacter('_'); urlEncoder.addSafeCharacter('.'); urlEncoder.addSafeCharacter('*'); urlEncoder.addSafeCharacter('/'); } // ----------------------------------------------------- Instance Variables private ResourceLocks fResLocks = null; private IWebdavStorage fStore = null; private IWebdavAuthorization fAuthorize = null; private IWebdavAlias fAliasManager = null; private static int fdebug = -1; private WebdavStoreFactory fFactory; private Hashtable fParameter; /** * Initialize this servlet. */ public void init() throws ServletException { try { md5Helper = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); throw new IllegalStateException(); } // Parameters from web.xml String clazz = getServletConfig().getInitParameter(WEBDAV_RESOURCE_IMPLEMENTATION); try { fFactory = new WebdavStoreFactory(WebdavServlet.class.getClassLoader().loadClass(clazz)); // parameter fParameter = new Hashtable(); Enumeration initParameterNames = getServletConfig().getInitParameterNames(); while (initParameterNames.hasMoreElements()) { String key = (String) initParameterNames.nextElement(); fParameter.put(key, getServletConfig().getInitParameter(key)); } String readonlyString = (String) fParameter.get(READONLY_PARAMETER); if (readonlyString == null || !readonlyString.equalsIgnoreCase("true")) readOnly = false; else readOnly = true; fStore = fFactory.getStore(); fResLocks = new ResourceLocks(); String debugString = (String) fParameter.get(DEBUG_PARAMETER); if (debugString == null) { fdebug = 0; } else { fdebug = Integer.parseInt(debugString); } String authImpl = getServletConfig().getInitParameter(WEBDAV_AUTHENTICATION_IMPLEMENTATION); if (authImpl != null && authImpl.trim().length() > 0) { WebdavAuthorizationFactory factory = new WebdavAuthorizationFactory( WebdavServlet.class.getClassLoader().loadClass(authImpl)); fAuthorize = factory.getAuthorization(); } String aliasImpl = getServletConfig().getInitParameter(WEBDAV_ALIAS_IMPLEMENTATION); if (aliasImpl != null && aliasImpl.trim().length() > 0) { WebdavAliasFactory factory = new WebdavAliasFactory( WebdavServlet.class.getClassLoader().loadClass(aliasImpl)); fAliasManager = factory.getAliasManager(); } } catch (Exception e) { throw new ServletException(e); } } /** * Handles the special WebDAV methods. */ protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, AuthenticationException { String method = req.getMethod(); if (fdebug == 1) { log.debug("WebdavServlet REQUEST: -----------------"); log.debug("Method = " + method); log.debug("Time: " + System.currentTimeMillis()); log.debug("Path: " + getRelativePath(req)); Enumeration e = req.getHeaderNames(); while (e.hasMoreElements()) { String s = (String) e.nextElement(); log.debug("Header: " + s + " " + req.getHeader(s)); } e = req.getAttributeNames(); while (e.hasMoreElements()) { String s = (String) e.nextElement(); log.debug("Attribute: " + s + " " + req.getAttribute(s)); } e = req.getParameterNames(); while (e.hasMoreElements()) { String s = (String) e.nextElement(); log.debug("Parameter: " + s + " " + req.getParameter(s)); } } try { fStore.begin(req, fParameter, getServletContext().getRealPath("/")); if (fAuthorize != null) fAuthorize.authorize(req); resp.setStatus(WebdavStatus.SC_OK); try { 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_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_GET)) { doGet(req, resp, true); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req, resp); } else if (method.equals(METHOD_HEAD)) { doHead(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else { super.service(req, resp); } fStore.commit(); } catch (IOException e) { log.error("WebdavServer internal error: ", e); resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); fStore.rollback(); throw new ServletException(e); } } catch (UnauthenticatedException e) { log.error("WebdavServer not authenticated: ", e); resp.sendError(WebdavStatus.SC_FORBIDDEN); } catch (WebdavException e) { log.error("WebdavServer internal error: ", e); throw new ServletException(e); } } /** * goes recursive through all folders. used by propfind * * @param currentPath * the current path * @param req * HttpServletRequest * @param generatedXML * @param propertyFindType * @param properties * @param depth * depth of the propfind * @throws IOException * if an error in the underlying store occurs */ private void recursiveParseProperties(String currentPath, HttpServletRequest req, XMLWriter generatedXML, int propertyFindType, Vector properties, int depth) throws WebdavException { parseProperties(req, generatedXML, currentPath, propertyFindType, properties); String[] names = fStore.getChildrenNames(currentPath); if ((names != null) && (depth > 0)) { for (int i = 0; i < names.length; i++) { String name = names[i]; String newPath = currentPath; if (!(newPath.endsWith("/"))) { newPath += "/"; } newPath += name; recursiveParseProperties(newPath, req, generatedXML, propertyFindType, properties, depth - 1); } } } /** * overwrites propNode and type, parsed from xml input stream * * @param propNode * @param type * @param req * HttpServletRequest * @throws ServletException */ private PropertyNodeType getPropertyNodeAndType(ServletRequest req) throws ServletException { PropertyNodeType nodeType = new PropertyNodeType(FIND_ALL_PROP); if (req.getContentLength() != 0) { DocumentBuilder documentBuilder = getDocumentBuilder(); try { Document document = documentBuilder.parse(new InputSource(req.getInputStream())); // Get the root element of the document Element rootElement = document.getDocumentElement(); NodeList childList = rootElement.getChildNodes(); for (int i = 0; i < childList.getLength(); i++) { Node currentNode = childList.item(i); switch (currentNode.getNodeType()) { case Node.TEXT_NODE: break; case Node.ELEMENT_NODE: if (currentNode.getNodeName().endsWith("prop")) { nodeType.setType(FIND_BY_PROPERTY); nodeType.setNode(currentNode); } if (currentNode.getNodeName().endsWith("propname")) { nodeType.setType(FIND_PROPERTY_NAMES); } if (currentNode.getNodeName().endsWith("allprop")) { nodeType.setType(FIND_ALL_PROP); } break; } } } catch (Exception e) { } } else { // no content, which means it is a allprop request nodeType.setType(FIND_ALL_PROP); } return nodeType; } private class PropertyNodeType { Node node = null; int type = FIND_ALL_PROP; public PropertyNodeType(int type) { this.type = type; } public PropertyNodeType(Node propNode, int type) { this.node = propNode; this.type = type; } /** * @return the node */ protected Node getNode() { return node; } /** * @param node the node to set */ protected void setNode(Node node) { this.node = node; } /** * @return the type */ protected int getType() { return type; } /** * @param type the type to set */ protected void setType(int type) { this.type = type; } } /** * Gets properties to be set from request * * @param req * @return Properties * @throws ServletException */ private Properties[] getPropertiesToSetOrRemove(ServletRequest req) throws ServletException { Properties[] props = new Properties[] { new Properties(), new Properties() }; if (req.getContentLength() == 0) return props; DocumentBuilder documentBuilder = getDocumentBuilder(); try { Document document = documentBuilder.parse(new InputSource(req.getInputStream())); // Get the root element of the document Element rootElement = document.getDocumentElement(); NodeList childList = rootElement.getChildNodes(); for (int i = 0; i < childList.getLength(); i++) { Node currentNode = childList.item(i); switch (currentNode.getNodeType()) { case Node.ELEMENT_NODE: if ("set".equals(currentNode.getLocalName())) { NodeList propList = currentNode.getChildNodes(); for (int j = 0; j < propList.getLength(); j++) { Node propNode = propList.item(j); if ("prop".equals(propNode.getLocalName())) { NodeList propNodes = propNode.getChildNodes(); for (int k = 0; k < propNodes.getLength(); k++) { Node node = propNodes.item(k); String pname = node.getNodeName(); if (pname == null || pname.trim().length() == 0) continue; String value = node.getTextContent(); props[0].setProperty(pname, value); } } } } else if ("remove".equals(currentNode.getLocalName())) { NodeList propList = currentNode.getChildNodes(); for (int j = 0; j < propList.getLength(); j++) { Node propNode = propList.item(j); if ("prop".equals(propNode.getLocalName())) { NodeList propNodes = propNode.getChildNodes(); for (int k = 0; k < propNodes.getLength(); k++) { Node node = propNodes.item(k); String pname = node.getNodeName(); if (pname == null || pname.trim().length() == 0) continue; String value = node.getTextContent(); props[1].setProperty(pname, value); } } } } break; case Node.TEXT_NODE: default: break; } } } catch (Exception e) { throw new ServletException("Unable to parse properties set/remove request"); } return props; } /** * creates the parent path from the given path by removing the last * '/' and everything after that * * @param path the path * @return parent path */ private String getParentPath(String path) { int slash = path.lastIndexOf('/'); if (slash != -1) { return path.substring(0, slash); } return null; } /** * Return JAXP document builder instance. */ private DocumentBuilder getDocumentBuilder() throws ServletException { DocumentBuilder documentBuilder = null; DocumentBuilderFactory documentBuilderFactory = null; try { documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setNamespaceAware(true); documentBuilder = documentBuilderFactory.newDocumentBuilder(); } catch (ParserConfigurationException e) { throw new ServletException("jaxp failed"); } return documentBuilder; } /** * Return the relative path associated with this servlet. * * @param request * The servlet request we are processing */ protected String getRelativePath(HttpServletRequest request) { // Are we being processed by a RequestDispatcher.include()? if (request.getAttribute("javax.servlet.include.request_uri") != null) { String result = (String) request.getAttribute("javax.servlet.include.path_info"); if (result == null) result = (String) request.getAttribute("javax.servlet.include.servlet_path"); if ((result == null) || (result.equals(""))) result = "/"; return (fAliasManager != null) ? fAliasManager.getResourceDestination(result) : result; } // No, extract the desired path directly from the request String servletPath = request.getServletPath(); String result = request.getPathInfo(); if (result == null) { result = servletPath + "/"; return (fAliasManager != null) ? fAliasManager.getResourceDestination(result) : result; } if (!result.startsWith(servletPath + "/")) { result = servletPath + result; } if ((result == null) || (result.equals(""))) { result = servletPath; } return (fAliasManager != null) ? fAliasManager.getResourceDestination(result) : result; } private Vector getPropertiesFromXML(Node propNode) { Vector properties; properties = new Vector(); 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; } } return properties; } /** * reads the depth header from the request and returns it as a int * * @param req * @return the depth from the depth header */ private int getDepth(HttpServletRequest req) { int depth = INFINITY; String depthStr = req.getHeader("Depth"); if (depthStr != null) { if (depthStr.equals("0")) { depth = 0; } else if (depthStr.equals("1")) { depth = 1; } else if (depthStr.equals("infinity")) { depth = INFINITY; } } return depth; } /** * removes a / at the end of the path string, if present * * @param path the path * @return the path without trailing / */ private String getCleanPath(String path) { if (path.endsWith("/") && path.length() > 1) path = path.substring(0, path.length() - 1); return path; } /** * OPTIONS Method.</br> * * * @param req * HttpServletRequest * @param resp * HttpServletResponse * @throws IOException * if an error in the underlying store occurs */ protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String lockOwner = "doOptions" + System.nanoTime() + req.toString(); String path = getRelativePath(req); if (fResLocks.lock(path, lockOwner, false, 0)) { try { resp.addHeader("DAV", "1, 2"); String methodsAllowed = determineMethodsAllowed(fStore.objectExists(path), fStore.isFolder(path)); resp.addHeader("Allow", methodsAllowed); resp.addHeader("MS-Author-Via", "DAV"); } catch (AccessDeniedException e) { log.error("WebdavServer not authenticated: ", e); resp.sendError(WebdavStatus.SC_FORBIDDEN); } catch (WebdavException e) { log.error("WebdavServer internal error: ", e); resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } finally { fResLocks.unlock(path, lockOwner); } } else { log.error("WebdavServer unable to lock resource " + lockOwner); resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } } /** * PROPFIND Method. * * @param req * HttpServletRequest * @param resp * HttpServletResponse * @throws IOException * if an error in the underlying store occurs * @throws ServletException */ protected void doPropfind(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // Retrieve the resources String lockOwner = "doPropfind" + System.nanoTime() + req.toString(); String path = getRelativePath(req); if ((path.toUpperCase().startsWith("/WEB-INF")) || (path.toUpperCase().startsWith("/META-INF"))) { log.error("WebdavServer cannot access /WEB-INF and /META-INF"); resp.sendError(WebdavStatus.SC_FORBIDDEN); return; } int depth = getDepth(req); if (fResLocks.lock(path, lockOwner, false, depth)) { try { if (!fStore.objectExists(path)) { resp.setStatus(WebdavStatus.SC_NOT_FOUND); return; // we do not to continue since there is no root // resource } Vector properties = null; path = getCleanPath(getRelativePath(req)); PropertyNodeType nodeType = getPropertyNodeAndType(req); if (nodeType.getType() == FIND_BY_PROPERTY) { properties = getPropertiesFromXML(nodeType.getNode()); } resp.setStatus(WebdavStatus.SC_MULTI_STATUS); resp.setContentType("text/xml; charset=UTF-8"); // Create multistatus object XMLWriter generatedXML = new XMLWriter(resp.getWriter()); generatedXML.writeXMLHeader(); generatedXML.writeElement(null, "multistatus" + generateNamespaceDeclarations(), XMLWriter.OPENING); if (depth == 0) { parseProperties(req, generatedXML, path, nodeType.getType(), properties); } else { recursiveParseProperties(path, req, generatedXML, nodeType.getType(), properties, depth); } generatedXML.writeElement(null, "multistatus", XMLWriter.CLOSING); generatedXML.sendData(); } catch (AccessDeniedException e) { log.error("WebdavServer not authenticated: ", e); resp.sendError(WebdavStatus.SC_FORBIDDEN); } catch (WebdavException e) { log.error("WebdavServer internal error: ", e); resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } finally { fResLocks.unlock(path, lockOwner); } } else { log.error("WebdavServer unable to lock resource " + lockOwner); resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } } /** * PROPPATCH Method. * * @param req * HttpServletRequest * @param resp * HttpServletResponse * @throws IOException * if an error in the underlying store occurs */ protected void doProppatch(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (readOnly) { log.error("WebdavServer not authenticated for write"); resp.sendError(WebdavStatus.SC_FORBIDDEN); } else { String lockOwner = "doProppatch" + System.nanoTime() + req.toString(); String path = getRelativePath(req); if ((path.toUpperCase().startsWith("/WEB-INF")) || (path.toUpperCase().startsWith("/META-INF"))) { log.error("WebdavServer cannot access /WEB-INF and /META-INF"); resp.sendError(WebdavStatus.SC_FORBIDDEN); return; } if (fResLocks.lock(path, lockOwner, false, -1)) { try { if (!fStore.objectExists(path)) { resp.setStatus(WebdavStatus.SC_NOT_FOUND); return; // we do not to continue since there is no root // resource } Properties[] properties = getPropertiesToSetOrRemove(req); // Set properties if (properties[0] != null && properties[0].size() > 0) fStore.setCustomProperties(path, properties[0]); // Remove properties if (properties[1] != null && properties[1].size() > 0) fStore.removeCustomProperties(path, properties[1]); } catch (WebdavException e) { log.error("WebdavServer internal error: ", e); resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } finally { fResLocks.unlock(path, lockOwner); } } else { log.error("WebdavServer unable to lock resource " + lockOwner); resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } } } /** * GET Method * * @param req * HttpServletRequest * @param resp * HttpServletResponse * @param includeBody * if the resource content should be included or not (GET/HEAD) * @throws IOException * if an error in the underlying store occurs */ protected void doGet(HttpServletRequest req, HttpServletResponse resp, boolean includeBody) throws ServletException, IOException { String lockOwner = "doGet" + System.nanoTime() + req.toString(); String path = getRelativePath(req); if (fResLocks.lock(path, lockOwner, false, 0)) { try { if (fStore.isResource(path)) { // path points to a file but ends with / or \ if (path.endsWith("/") || (path.endsWith("\\"))) { resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI()); } else { // setting headers long lastModified = fStore.getLastModified(path).getTime(); resp.setDateHeader("last-modified", lastModified); long resourceLength = fStore.getResourceLength(path); if (resourceLength > 0) { if (resourceLength <= Integer.MAX_VALUE) { resp.setContentLength((int) resourceLength); } else { resp.setHeader("content-length", "" + resourceLength); // is "content-length" the right header? is long // a valid format? } } String mimeType = getServletContext().getMimeType(path); if (mimeType != null) { resp.setContentType(mimeType); } if (includeBody) { OutputStream out = resp.getOutputStream(); InputStream in = fStore.getResourceContent(path); try { int read = -1; byte[] copyBuffer = new byte[BUF_SIZE]; while ((read = in.read(copyBuffer, 0, copyBuffer.length)) != -1) { out.write(copyBuffer, 0, read); } } finally { in.close(); out.flush(); out.close(); } } } } else { if (includeBody && fStore.isFolder(path)) { renderHtml(path, resp.getOutputStream(), req.getContextPath()); resp.setContentType("text/html"); } else { if (!fStore.objectExists(path) && includeBody) { resp.setStatus(HttpServletResponse.SC_NOT_FOUND); } else { // Check if we're included so we can return the appropriate // missing resource name in the error String requestUri = (String) req.getAttribute("javax.servlet.include.request_uri"); if (requestUri == null) { requestUri = req.getRequestURI(); } else { // We're included, and the response.sendError() below is going // to be ignored by the resource that is including us. // Therefore, the only way we can let the including resource // know is by including warning message in response resp.getWriter() .write("The requested resource (" + requestUri + ") is not available"); } resp.sendError(HttpServletResponse.SC_NOT_FOUND, requestUri); } } } } catch (AccessDeniedException e) { log.error("WebdavServer not authenticated: ", e); resp.sendError(WebdavStatus.SC_FORBIDDEN); } catch (ObjectAlreadyExistsException e) { resp.sendError(WebdavStatus.SC_NOT_FOUND, req.getRequestURI()); } catch (WebdavException e) { log.error("WebdavServer internal error: ", e); resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } finally { fResLocks.unlock(path, lockOwner); } } else { log.error("WebdavServer unable to lock resource " + lockOwner); resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } } /** * Return an InputStream to an HTML representation of the contents * of this directory. * * @param contextPath Context path to which our internal paths are * relative */ protected void renderHtml(String contextPath, OutputStream stream, String prefix) throws IOException, ServletException { String path = contextPath; // Prepare a writer to a buffered area OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8"); PrintWriter writer = new PrintWriter(osWriter); StringBuffer sb = new StringBuffer(); // rewriteUrl(contextPath) is expensive. cache result for later reuse String rewrittenContextPath = rewriteUrl(contextPath); // Render the page header sb.append("<html>\r\n"); sb.append("<head>\r\n"); sb.append("<title>"); sb.append("Directory " + path); sb.append("</title>\r\n"); sb.append("<STYLE><!--"); sb.append(TOMCAT_CSS); sb.append("--></STYLE> "); sb.append("</head>\r\n"); sb.append("<body>"); sb.append("<h1>"); sb.append("Directory " + path); // Render the link to our parent (if required) String parentDirectory = path; if (parentDirectory.endsWith("/")) { parentDirectory = parentDirectory.substring(0, parentDirectory.length() - 1); } int slash = parentDirectory.lastIndexOf('/'); if (slash >= 0) { String parent = path.substring(0, slash); if (parent.trim().length() > 0) { sb.append(" - <a href=\""); if (prefix != null) { sb.append(prefix); } sb.append(rewriteUrl(parent)); if (!parent.endsWith("/")) sb.append("/"); sb.append("\">"); sb.append("<b>"); sb.append("Parent Directory " + parent); sb.append("</b>"); sb.append("</a>"); } } sb.append("</h1>"); sb.append("<HR size=\"1\" noshade=\"noshade\">"); sb.append("<table width=\"100%\" cellspacing=\"0\"" + " cellpadding=\"5\" align=\"center\">\r\n"); // Render the column headings sb.append("<tr>\r\n"); sb.append("<td align=\"left\"><font size=\"+1\"><strong>"); sb.append("Filename"); sb.append("</strong></font></td>\r\n"); sb.append("<td align=\"center\"><font size=\"+1\"><strong>"); sb.append("Size"); sb.append("</strong></font></td>\r\n"); sb.append("<td align=\"right\"><font size=\"+1\"><strong>"); sb.append("lastModified"); sb.append("</strong></font></td>\r\n"); sb.append("</tr>"); try { // Render the directory entries within this directory String[] children = fStore.getChildrenNames(contextPath); boolean shade = false; for (int i = 0; i < children.length; i++) { String child = children[i]; String resourceName = child; String trimmed = resourceName/*.substring(trim)*/; if (trimmed.equalsIgnoreCase("WEB-INF") || trimmed.equalsIgnoreCase("META-INF")) continue; String resourcePath = rewrittenContextPath; if (!resourcePath.endsWith("/")) { resourcePath += "/"; } resourcePath += resourceName; boolean exists = fStore.objectExists(resourcePath); if (!exists) { continue; } sb.append("<tr"); if (shade) sb.append(" bgcolor=\"#eeeeee\""); sb.append(">\r\n"); shade = !shade; boolean isDir = fStore.isFolder(resourcePath); sb.append("<td align=\"left\"> \r\n"); sb.append("<a href=\""); if (prefix != null) sb.append(prefix); sb.append(resourcePath); if (isDir) sb.append("/"); sb.append("\"><tt>"); sb.append(RequestUtil.filter(trimmed)); if (isDir) sb.append("/"); sb.append("</tt></a></td>\r\n"); sb.append("<td align=\"right\"><tt>"); if (isDir) sb.append(" "); else { try { sb.append(renderSize(fStore.getResourceLength(resourcePath))); } catch (WebdavException e) { sb.append(renderSize(-1)); } } sb.append("</tt></td>\r\n"); sb.append("<td align=\"right\"><tt>"); try { sb.append(fStore.getLastModified(resourcePath)); } catch (WebdavException e) { sb.append("?"); } sb.append("</tt></td>\r\n"); sb.append("</tr>\r\n"); } } catch (WebdavException e) { // Something went wrong throw new ServletException("Error accessing resource", e); } // Render the page footer sb.append("</table>\r\n"); sb.append("<HR size=\"1\" noshade=\"noshade\">"); sb.append("</body>\r\n"); sb.append("</html>\r\n"); writer.write(sb.toString()); writer.flush(); } /** * Render the specified file size (in bytes). * * @param size File size (in bytes) */ protected String renderSize(long size) { long leftSide = size / 1024; long rightSide = (size % 1024) / 103; // Makes 1 digit if ((leftSide == 0) && (rightSide == 0) && (size > 0)) rightSide = 1; return ("" + leftSide + "." + rightSide + " kb"); } /** * HEAD Method. * * @param req * HttpServletRequest * @param resp * HttpServletResponse * @throws IOException * if an error in the underlying store occurs */ protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp, false); } /** * MKCOL Method. * * @param req * HttpServletRequest * @param resp * HttpServletResponse * @throws IOException * if an error in the underlying store occurs */ protected void doMkcol(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (req.getContentLength() > 0) { resp.sendError(WebdavStatus.SC_NOT_IMPLEMENTED); } else { if (!readOnly) { // not readonly String path = getRelativePath(req); String parentPath = getParentPath(path); String lockOwner = "doMkcol" + System.nanoTime() + req.toString(); if (fResLocks.lock(path, lockOwner, true, 0)) { try { if (parentPath != null && fStore.isFolder(parentPath)) { boolean isFolder = fStore.isFolder(path); if (!fStore.objectExists(path)) { try { fStore.createFolder(path); resp.setStatus(WebdavStatus.SC_CREATED); } catch (ObjectAlreadyExistsException e) { String methodsAllowed = determineMethodsAllowed(true, isFolder); resp.addHeader("Allow", methodsAllowed); resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED); } } else { // object already exists String methodsAllowed = determineMethodsAllowed(true, isFolder); resp.addHeader("Allow", methodsAllowed); resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED); } } else { resp.sendError(WebdavStatus.SC_CONFLICT); } } catch (AccessDeniedException e) { log.error("WebdavServer not authenticated: ", e); resp.sendError(WebdavStatus.SC_FORBIDDEN); } catch (WebdavException e) { log.error("WebdavServer internal error: ", e); resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } finally { fResLocks.unlock(path, lockOwner); } } else { log.error("WebdavServer unable to lock resource " + lockOwner); resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } } else { log.error("WebdavServer not authenticated for write"); resp.sendError(WebdavStatus.SC_FORBIDDEN); } } } /** * DELETE Method. * * @param req * HttpServletRequest * @param resp * HttpServletResponse * @throws IOException * if an error in the underlying store occurs */ protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (!readOnly) { String path = getRelativePath(req); String lockOwner = "doDelete" + System.nanoTime() + req.toString(); if (fResLocks.lock(path, lockOwner, true, -1)) { try { Hashtable errorList = new Hashtable(); deleteResource(path, errorList, req, resp); if (!errorList.isEmpty()) { sendReport(req, resp, errorList); } } catch (AccessDeniedException e) { log.error("WebdavServer not authenticated: ", e); resp.sendError(WebdavStatus.SC_FORBIDDEN); } catch (ObjectAlreadyExistsException e) { resp.sendError(WebdavStatus.SC_NOT_FOUND, req.getRequestURI()); } catch (WebdavException e) { log.error("WebdavServer internal error: ", e); resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } finally { fResLocks.unlock(path, lockOwner); } } else { log.error("WebdavServer unable to lock resource " + lockOwner); resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } } else { log.error("WebdavServer not authenticated for write"); resp.sendError(WebdavStatus.SC_FORBIDDEN); } } /** * Process a POST request for the specified resource. * * @param req * The servlet request we are processing * @param resp * The servlet response we are creating * * @exception WebdavException * if an error in the underlying store occurs */ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (!readOnly) { String path = getRelativePath(req); String parentPath = getParentPath(path); String lockOwner = "doPut" + System.nanoTime() + req.toString(); if (fResLocks.lock(path, lockOwner, true, -1)) { try { if (parentPath != null && fStore.isFolder(parentPath) && !fStore.isFolder(path)) { if (!fStore.objectExists(path)) { fStore.createResource(path); resp.setStatus(HttpServletResponse.SC_CREATED); } else { resp.setStatus(HttpServletResponse.SC_NO_CONTENT); } fStore.setResourceContent(path, req.getInputStream(), null, null); resp.setContentLength((int) fStore.getResourceLength(path)); } else { resp.sendError(WebdavStatus.SC_CONFLICT); } } catch (AccessDeniedException e) { log.error("WebdavServer not authenticated: ", e); resp.sendError(WebdavStatus.SC_FORBIDDEN); } catch (WebdavException e) { log.error("WebdavServer internal error: ", e); resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } finally { fResLocks.unlock(path, lockOwner); } } else { log.error("WebdavServer unable to lock resource " + lockOwner); resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } } else { resp.sendError(WebdavStatus.SC_FORBIDDEN); } } /** * COPY Method. * * @param req * HttpServletRequest * @param resp * HttpServletResponse * @throws WebdavException * if an error in the underlying store occurs * @throws IOException * when an error occurs while sending the response */ protected void doCopy(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String path = getRelativePath(req); if (!readOnly) { String lockOwner = "doCopy" + System.nanoTime() + req.toString(); if (fResLocks.lock(path, lockOwner, false, -1)) { try { copyResource(req, resp); } catch (AccessDeniedException e) { log.error("WebdavServer not authenticated: ", e); resp.sendError(WebdavStatus.SC_FORBIDDEN); } catch (ObjectAlreadyExistsException e) { resp.sendError(WebdavStatus.SC_CONFLICT, req.getRequestURI()); } catch (ObjectNotFoundException e) { resp.sendError(WebdavStatus.SC_NOT_FOUND, req.getRequestURI()); } catch (WebdavException e) { log.error("WebdavServer internal error: ", e); resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } finally { fResLocks.unlock(path, lockOwner); } } else { log.error("WebdavServer unable to lock resource " + lockOwner); resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } } else { log.error("WebdavServer not authenticated for write"); resp.sendError(WebdavStatus.SC_FORBIDDEN); } } /** * MOVE Method. * * @param req * HttpServletRequest * @param resp * HttpServletResponse * @throws ServletException * @throws WebdavException * if an error in the underlying store occurs * @throws IOException * when an error occurs while sending the response */ protected void doMove(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (!readOnly) { String path = getRelativePath(req); String lockOwner = "doMove" + System.nanoTime() + req.toString(); if (fResLocks.lock(path, lockOwner, false, -1)) { try { String destinationPath = copyResource(req, resp); if (destinationPath != null) { Hashtable errorList = new Hashtable(); deleteResource(path, errorList, req, resp); if (!errorList.isEmpty()) { sendReport(req, resp, errorList); } if (fAliasManager != null) fAliasManager.resourceMovedNotification(path, destinationPath); } else { log.error("Destination directory in doMove cannot be empty"); resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } } catch (AccessDeniedException e) { log.error("WebdavServer not authenticated: ", e); resp.sendError(WebdavStatus.SC_FORBIDDEN); } catch (ObjectAlreadyExistsException e) { resp.sendError(WebdavStatus.SC_NOT_FOUND, req.getRequestURI()); } catch (WebdavException e) { log.error("WebdavServer internal error: ", e); resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } finally { fResLocks.unlock(path, lockOwner); } } else { log.error("WebdavServer unable to lock resource " + lockOwner); resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } } else { log.error("WebdavServer not authenticated for write"); resp.sendError(WebdavStatus.SC_FORBIDDEN); } } // -------------------------------------------------------- Private Methods /** * Generate the namespace declarations. * @return the namespace declaration */ private String generateNamespaceDeclarations() { return " xmlns=\"" + DEFAULT_NAMESPACE + "\""; } /** * Copy a resource. * * @param req * Servlet request * @param resp * Servlet response * @return true if the copy is successful * @throws WebdavException * if an error in the underlying store occurs * @throws IOException * when an error occurs while sending the response */ private String copyResource(HttpServletRequest req, HttpServletResponse resp) throws WebdavException, IOException { // Parsing destination header String destinationPath = req.getHeader("Destination"); if (destinationPath == null) { resp.sendError(WebdavStatus.SC_BAD_REQUEST); 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 = normalize(destinationPath); String contextPath = req.getContextPath(); if ((contextPath != null) && (destinationPath.startsWith(contextPath))) { destinationPath = destinationPath.substring(contextPath.length()); } String path = getRelativePath(req); // if source = destination if (path.equals(destinationPath)) { log.error("WebdavServer cannot copy if source and destination is the same"); resp.sendError(HttpServletResponse.SC_FORBIDDEN); } // Parsing overwrite header boolean overwrite = true; String overwriteHeader = req.getHeader("Overwrite"); if (overwriteHeader != null) { overwrite = overwriteHeader.equalsIgnoreCase("T"); } // Overwriting the destination String lockOwner = "copyResource" + System.nanoTime() + req.toString(); if (fResLocks.lock(destinationPath, lockOwner, true, -1)) { try { // Retrieve the resources if (!fStore.objectExists(path)) { log.error("Recource to be copied does not exist " + path); resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return null; } Hashtable errorList = new Hashtable(); copy(path, destinationPath, overwrite, errorList, req, resp); if (!errorList.isEmpty()) { sendReport(req, resp, errorList); } } finally { fResLocks.unlock(destinationPath, lockOwner); } } else { log.error("WebdavServer unable to lock resource " + lockOwner); resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); return null; } return destinationPath; } /** * copies the specified resource(s) to the specified destination. * preconditions must be handled by the caller. Standard status codes must * be handled by the caller. a multi status report in case of errors is * created here. * * @param sourcePath * path from where to read * @param destinationPath * path where to write * @param req * HttpServletRequest * @param resp * HttpServletResponse * @throws WebdavException * if an error in the underlying store occurs * @throws IOException */ private void copy(String sourcePath, String destinationPath, boolean overwrite, Hashtable errorList, HttpServletRequest req, HttpServletResponse resp) throws WebdavException, IOException { if (fStore.isResource(sourcePath)) { if (fStore.isFolder(destinationPath)) { copyFolder(sourcePath, destinationPath, overwrite, errorList, req, resp); } else { checkOverwriteResource(destinationPath, overwrite, errorList, req, resp); fStore.copyResource(sourcePath, destinationPath); } } else { if (fStore.isFolder(sourcePath)) { copyFolder(sourcePath, destinationPath, overwrite, errorList, req, resp); } else { resp.sendError(WebdavStatus.SC_NOT_FOUND); } } } /** * helper method of copy() recursively copies the FOLDER at source path to * destination path * * @param sourcePath * where to read * @param destinationPath * where to write * @param errorList * all errors that occurred * @param req * HttpServletRequest * @param resp * HttpServletResponse * @throws WebdavException * if an error in the underlying store occurs */ private void copyFolder(String sourcePath, String destinationPath, boolean overwrite, Hashtable errorList, HttpServletRequest req, HttpServletResponse resp) throws WebdavException { if (!fStore.objectExists(destinationPath)) fStore.createFolder(destinationPath); boolean infiniteDepth = true; if (req.getHeader("depth") != null) { if (req.getHeader("depth").equals("0")) { infiniteDepth = false; } } if (infiniteDepth) { String[] children = fStore.getChildrenNames(sourcePath); if (children == null || children.length == 0) { if (fStore.isResource(sourcePath)) { String resourceName = fStore.getResourceName(sourcePath); checkOverwriteResource(destinationPath + "/" + resourceName, overwrite, errorList, req, resp); fStore.copyResource(sourcePath, destinationPath + "/" + resourceName); } } else { for (int i = children.length - 1; i >= 0; i--) { children[i] = "/" + children[i]; try { if (fStore.isResource(sourcePath + children[i])) { checkOverwriteResource(destinationPath + children[i], overwrite, errorList, req, resp); fStore.copyResource(sourcePath + children[i], destinationPath + children[i]); } else { copyFolder(sourcePath + children[i], destinationPath + children[i], overwrite, errorList, req, resp); } } catch (AccessDeniedException e) { errorList.put(destinationPath + children[i], new Integer(WebdavStatus.SC_FORBIDDEN)); } catch (ObjectNotFoundException e) { errorList.put(destinationPath + children[i], new Integer(WebdavStatus.SC_NOT_FOUND)); } catch (ObjectAlreadyExistsException e) { errorList.put(destinationPath + children[i], new Integer(WebdavStatus.SC_CONFLICT)); } catch (WebdavException e) { log.error("WebdavServer internal error in copyFolder: " + (destinationPath + children[i]), e); errorList.put(destinationPath + children[i], new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR)); } } } } } private void checkOverwriteResource(String path, boolean overwrite, Hashtable errorList, HttpServletRequest req, HttpServletResponse resp) throws WebdavException { try { boolean exists = fStore.objectExists(path); if (overwrite) { // Delete destination resource, if it exists and it is not directory if (exists && !fStore.isFolder(path)) { deleteResource(path, errorList, req, resp); } else { resp.setStatus(WebdavStatus.SC_CREATED); } } else { // If the destination exists, then it's a conflict if (exists) { resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED); return; } else { resp.setStatus(WebdavStatus.SC_CREATED); } } } catch (Exception e) { throw new WebdavException(e); } } /** * deletes the resources at "path" * * @param path * the folder to be deleted * @param errorList * all errors that occurred * @param req * HttpServletRequest * @param resp * HttpServletResponse * @throws WebdavException * if an error in the underlying store occurs * @throws IOException * when an error occurs while sending the response */ private void deleteResource(String path, Hashtable errorList, HttpServletRequest req, HttpServletResponse resp) throws IOException, WebdavException { resp.setStatus(WebdavStatus.SC_NO_CONTENT); if (!readOnly) { if (fStore.isResource(path)) { fStore.removeObject(path); } else { if (fStore.isFolder(path)) { deleteFolder(path, errorList, req, resp); fStore.removeObject(path); } else { resp.sendError(WebdavStatus.SC_NOT_FOUND); } } } else { log.error("WebdavServer not authenticated for write"); resp.sendError(WebdavStatus.SC_FORBIDDEN); } } /** * * helper method of deleteResource() deletes the folder and all of its * contents * * @param path * the folder to be deleted * @param errorList * all errors that ocurred * @param req * HttpServletRequest * @param resp * HttpServletResponse * @throws WebdavException * if an error in the underlying store occurs */ private void deleteFolder(String path, Hashtable errorList, HttpServletRequest req, HttpServletResponse resp) throws WebdavException { String[] children = fStore.getChildrenNames(path); for (int i = children.length - 1; i >= 0; i--) { children[i] = "/" + children[i]; try { if (fStore.isResource(path + children[i])) { fStore.removeObject(path + children[i]); } else { deleteFolder(path + children[i], errorList, req, resp); fStore.removeObject(path + children[i]); } } catch (AccessDeniedException e) { errorList.put(path + children[i], new Integer(WebdavStatus.SC_FORBIDDEN)); } catch (ObjectNotFoundException e) { errorList.put(path + children[i], new Integer(WebdavStatus.SC_NOT_FOUND)); } catch (WebdavException e) { log.error("WebdavServer internal error in copyFolder: " + (path + children[i]), e); errorList.put(path + children[i], new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR)); } } } /** * Return a context-relative path, beginning with a "/", that represents the * canonical version of the specified path after ".." and "." elements are * resolved out. If the specified path attempts to go outside the boundaries * of the current context (i.e. too many ".." path elements are present), * return <code>null</code> instead. * * @param path * Path to be normalized */ protected String normalize(String path) { if (path == null) return null; // Create a place for the normalized path String normalized = path; if (normalized.equals("/.")) return "/"; // Normalize the slashes and add leading slash if necessary if (normalized.indexOf('\\') >= 0) normalized = normalized.replace('\\', '/'); if (!normalized.startsWith("/")) normalized = "/" + normalized; // Resolve occurrences of "//" in the normalized path while (true) { int index = normalized.indexOf("//"); if (index < 0) break; normalized = normalized.substring(0, index) + normalized.substring(index + 1); } // Resolve occurrences of "/./" in the normalized path while (true) { int index = normalized.indexOf("/./"); if (index < 0) break; normalized = normalized.substring(0, index) + normalized.substring(index + 2); } // Resolve occurrences of "/../" in the normalized path while (true) { int index = normalized.indexOf("/../"); if (index < 0) break; if (index == 0) return (null); // Trying to go outside our context int index2 = normalized.lastIndexOf('/', index - 1); normalized = normalized.substring(0, index2) + normalized.substring(index + 3); } // Return the normalized path that we have completed return (normalized); } /** * Send a multistatus element containing a complete error report to the * client. * * @param req * Servlet request * @param resp * Servlet response * @param errorList * List of error to be displayed */ private void sendReport(HttpServletRequest req, HttpServletResponse resp, Hashtable errorList) throws IOException { resp.setStatus(WebdavStatus.SC_MULTI_STATUS); String absoluteUri = req.getRequestURI(); String relativePath = getRelativePath(req); XMLWriter generatedXML = new XMLWriter(); generatedXML.writeXMLHeader(); generatedXML.writeElement(null, "multistatus" + generateNamespaceDeclarations(), XMLWriter.OPENING); Enumeration pathList = errorList.keys(); while (pathList.hasMoreElements()) { String errorPath = (String) pathList.nextElement(); int errorCode = ((Integer) errorList.get(errorPath)).intValue(); generatedXML.writeElement(null, "response", XMLWriter.OPENING); generatedXML.writeElement(null, "href", XMLWriter.OPENING); String toAppend = errorPath.substring(relativePath.length()); if (!toAppend.startsWith("/")) toAppend = "/" + toAppend; generatedXML.writeText(absoluteUri + toAppend); generatedXML.writeElement(null, "href", XMLWriter.CLOSING); generatedXML.writeElement(null, "status", XMLWriter.OPENING); generatedXML.writeText("HTTP/1.1 " + errorCode + " " + WebdavStatus.getStatusText(errorCode)); generatedXML.writeElement(null, "status", XMLWriter.CLOSING); generatedXML.writeElement(null, "response", XMLWriter.CLOSING); } generatedXML.writeElement(null, "multistatus", XMLWriter.CLOSING); Writer writer = resp.getWriter(); writer.write(generatedXML.toString()); writer.close(); } /** * Propfind helper method. * * @param req * The servlet request * @param generatedXML * XML response to the Propfind request * @param path * Path of the current resource * @param type * Propfind type * @param propertiesVector * If the propfind type is find properties by name, then this * Vector contains those properties */ private void parseProperties(HttpServletRequest req, XMLWriter generatedXML, String path, int type, Vector propertiesVector) throws WebdavException { String creationdate = getISOCreationDate(fStore.getCreationDate(path).getTime()); boolean isFolder = fStore.isFolder(path); SimpleDateFormat formatter = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); formatter.setTimeZone(TimeZone.getTimeZone("GMT")); String lastModified = formatter.format(fStore.getLastModified(path)); String resourceLength = String.valueOf(fStore.getResourceLength(path)); // ResourceInfo resourceInfo = new ResourceInfo(path, resources); generatedXML.writeElement(null, "response", XMLWriter.OPENING); String status = new String( "HTTP/1.1 " + WebdavStatus.SC_OK + " " + WebdavStatus.getStatusText(WebdavStatus.SC_OK)); // Generating href element generatedXML.writeElement(null, "href", XMLWriter.OPENING); String href = req.getContextPath(); if ((href.endsWith("/")) && (path.startsWith("/"))) href += path.substring(1); else href += path; if ((isFolder) && (!href.endsWith("/"))) href += "/"; generatedXML.writeText(rewriteUrl(href)); generatedXML.writeElement(null, "href", XMLWriter.CLOSING); String resourceName = path; int lastSlash = path.lastIndexOf('/'); if (lastSlash != -1) resourceName = resourceName.substring(lastSlash + 1); Properties customProperties; Enumeration en; switch (type) { case FIND_ALL_PROP: generatedXML.writeElement(null, "propstat", XMLWriter.OPENING); generatedXML.writeElement(null, "prop", XMLWriter.OPENING); generatedXML.writeProperty(null, "creationdate", creationdate); generatedXML.writeElement(null, "displayname", XMLWriter.OPENING); generatedXML.writeData(resourceName); generatedXML.writeElement(null, "displayname", XMLWriter.CLOSING); if (!isFolder) { generatedXML.writeProperty(null, "getlastmodified", lastModified); generatedXML.writeProperty(null, "getcontentlength", resourceLength); String contentType = getServletContext().getMimeType(path); if (contentType != null) { generatedXML.writeProperty(null, "getcontenttype", contentType); } generatedXML.writeProperty(null, "getetag", getETag(path, resourceLength, lastModified)); generatedXML.writeElement(null, "resourcetype", XMLWriter.NO_CONTENT); } else { generatedXML.writeElement(null, "resourcetype", XMLWriter.OPENING); generatedXML.writeElement(null, "collection", XMLWriter.NO_CONTENT); generatedXML.writeElement(null, "resourcetype", XMLWriter.CLOSING); } // Get custom properties customProperties = fStore.getCustomProperties(path); if (customProperties != null && customProperties.size() > 0) { en = customProperties.keys(); while (en.hasMoreElements()) { String key = (String) en.nextElement(); generatedXML.writeProperty(null, key, customProperties.getProperty(key)); } } generatedXML.writeProperty(null, "source", ""); generatedXML.writeElement(null, "prop", XMLWriter.CLOSING); generatedXML.writeElement(null, "status", XMLWriter.OPENING); generatedXML.writeText(status); generatedXML.writeElement(null, "status", XMLWriter.CLOSING); generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING); break; case FIND_PROPERTY_NAMES: generatedXML.writeElement(null, "propstat", XMLWriter.OPENING); generatedXML.writeElement(null, "prop", XMLWriter.OPENING); generatedXML.writeElement(null, "creationdate", XMLWriter.NO_CONTENT); generatedXML.writeElement(null, "displayname", XMLWriter.NO_CONTENT); if (!isFolder) { generatedXML.writeElement(null, "getcontentlanguage", XMLWriter.NO_CONTENT); generatedXML.writeElement(null, "getcontentlength", XMLWriter.NO_CONTENT); generatedXML.writeElement(null, "getcontenttype", XMLWriter.NO_CONTENT); generatedXML.writeElement(null, "getetag", XMLWriter.NO_CONTENT); generatedXML.writeElement(null, "getlastmodified", XMLWriter.NO_CONTENT); } generatedXML.writeElement(null, "resourcetype", XMLWriter.NO_CONTENT); generatedXML.writeElement(null, "source", XMLWriter.NO_CONTENT); generatedXML.writeElement(null, "lockdiscovery", XMLWriter.NO_CONTENT); // Get custom properties customProperties = fStore.getCustomProperties(path); if (customProperties != null && customProperties.size() > 0) { customProperties = fStore.getCustomProperties(path); en = customProperties.keys(); while (en.hasMoreElements()) { String key = (String) en.nextElement(); generatedXML.writeElement(null, key, XMLWriter.NO_CONTENT); } } generatedXML.writeElement(null, "prop", XMLWriter.CLOSING); generatedXML.writeElement(null, "status", XMLWriter.OPENING); generatedXML.writeText(status); generatedXML.writeElement(null, "status", XMLWriter.CLOSING); generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING); break; case FIND_BY_PROPERTY: Vector propertiesNotFound = new Vector(); // Parse the list of properties generatedXML.writeElement(null, "propstat", XMLWriter.OPENING); generatedXML.writeElement(null, "prop", XMLWriter.OPENING); Enumeration properties = propertiesVector.elements(); customProperties = fStore.getCustomProperties(path); while (properties.hasMoreElements()) { String property = (String) properties.nextElement(); if (property.equals("creationdate")) { generatedXML.writeProperty(null, "creationdate", creationdate); } else if (property.equals("displayname")) { generatedXML.writeElement(null, "displayname", XMLWriter.OPENING); generatedXML.writeData(resourceName); generatedXML.writeElement(null, "displayname", XMLWriter.CLOSING); } else if (property.equals("getcontentlanguage")) { if (isFolder) { propertiesNotFound.addElement(property); } else { generatedXML.writeElement(null, "getcontentlanguage", XMLWriter.NO_CONTENT); } } else if (property.equals("getcontentlength")) { if (isFolder) { propertiesNotFound.addElement(property); } else { generatedXML.writeProperty(null, "getcontentlength", resourceLength); } } else if (property.equals("getcontenttype")) { if (isFolder) { propertiesNotFound.addElement(property); } else { generatedXML.writeProperty(null, "getcontenttype", getServletContext().getMimeType(path)); } } else if (property.equals("getetag")) { if (isFolder) { propertiesNotFound.addElement(property); } else { generatedXML.writeProperty(null, "getetag", getETag(path, resourceLength, lastModified)); } } else if (property.equals("getlastmodified")) { if (isFolder) { propertiesNotFound.addElement(property); } else { generatedXML.writeProperty(null, "getlastmodified", lastModified); } } else if (property.equals("resourcetype")) { if (isFolder) { generatedXML.writeElement(null, "resourcetype", XMLWriter.OPENING); generatedXML.writeElement(null, "collection", XMLWriter.NO_CONTENT); generatedXML.writeElement(null, "resourcetype", XMLWriter.CLOSING); } else { generatedXML.writeElement(null, "resourcetype", XMLWriter.NO_CONTENT); } } else if (property.equals("source")) { generatedXML.writeProperty(null, "source", ""); } else if (customProperties != null && customProperties.containsKey(property)) { generatedXML.writeProperty(null, property, customProperties.getProperty(property)); } else { propertiesNotFound.addElement(property); } } generatedXML.writeElement(null, "prop", XMLWriter.CLOSING); generatedXML.writeElement(null, "status", XMLWriter.OPENING); generatedXML.writeText(status); generatedXML.writeElement(null, "status", XMLWriter.CLOSING); generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING); Enumeration propertiesNotFoundList = propertiesNotFound.elements(); if (propertiesNotFoundList.hasMoreElements()) { status = new String("HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND + " " + WebdavStatus.getStatusText(WebdavStatus.SC_NOT_FOUND)); generatedXML.writeElement(null, "propstat", XMLWriter.OPENING); generatedXML.writeElement(null, "prop", XMLWriter.OPENING); while (propertiesNotFoundList.hasMoreElements()) { generatedXML.writeElement(null, (String) propertiesNotFoundList.nextElement(), XMLWriter.NO_CONTENT); } generatedXML.writeElement(null, "prop", XMLWriter.CLOSING); generatedXML.writeElement(null, "status", XMLWriter.OPENING); generatedXML.writeText(status); generatedXML.writeElement(null, "status", XMLWriter.CLOSING); generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING); } break; } generatedXML.writeElement(null, "response", XMLWriter.CLOSING); } /** * Get the ETag associated with a file. * * @param path * path to the resource * @param resourceLength * filesize * @param lastModified * last-modified date * @return the ETag */ protected String getETag(String path, String resourceLength, String lastModified) { // TODO create a real (?) ETag // parameter "path" is not used at the moment return "W/\"" + resourceLength + "-" + lastModified + "\""; } /** * URL rewriter. * * @param path * Path which has to be rewiten * @return the rewritten path */ protected String rewriteUrl(String path) { return urlEncoder.encode(path); } /** * Get creation date in ISO format. * * @param creationDate * the date in milliseconds * @return the Date in ISO format */ private String getISOCreationDate(long creationDate) { StringBuffer creationDateValue = new StringBuffer(creationDateFormat.format(new Date(creationDate))); /* * int offset = Calendar.getInstance().getTimeZone().getRawOffset() / * 3600000; // FIXME ? if (offset < 0) { creationDateValue.append("-"); * offset = -offset; } else if (offset > 0) { * creationDateValue.append("+"); } if (offset != 0) { if (offset < 10) * creationDateValue.append("0"); creationDateValue.append(offset + * ":00"); } else { creationDateValue.append("Z"); } */ return creationDateValue.toString(); } /** * Determines the methods normally allowed for the resource. * * @param exists * does the resource exist? * @param isFolder * is the resource a folder? * @return all allowed methods, separated by commas */ private String determineMethodsAllowed(boolean exists, boolean isFolder) { StringBuffer methodsAllowed = new StringBuffer(); try { if (exists) { methodsAllowed.append("OPTIONS, GET, HEAD, POST, DELETE, TRACE"); methodsAllowed.append(", PROPPATCH, COPY, MOVE, LOCK, UNLOCK, PROPFIND"); if (isFolder) { methodsAllowed.append(", PUT"); } return methodsAllowed.toString(); } } catch (Exception e) { // we do nothing, just return less allowed methods } methodsAllowed.append("OPTIONS, MKCOL, PUT, LOCK"); return methodsAllowed.toString(); } }