/* * Copyright (C) 2005 Alfresco, Inc. * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a * copy of the License at * * http://www.alfresco.org/legal/license.txt * * 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.alfresco.repo.webdav; import java.io.IOException; import java.util.Date; import java.util.HashMap; import java.util.Iterator; 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.alfresco.model.ContentModel; import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.transaction.TransactionUtil; import org.alfresco.repo.transaction.TransactionUtil.TransactionWork; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.lock.LockService; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.security.AuthenticationService; import org.alfresco.service.transaction.TransactionService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.dom4j.DocumentHelper; import org.dom4j.io.OutputFormat; import org.dom4j.io.XMLWriter; import org.w3c.dom.Document; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * Abstract base class for all the WebDAV method handling classes * * @author gavinc */ public abstract class WebDAVMethod { // Log output protected static Log logger = LogFactory.getLog("org.alfresco.webdav.protocol"); // Output formatted XML in the response private static final boolean XMLPrettyPrint = true; // Servlet request/response protected HttpServletRequest m_request; protected HttpServletResponse m_response; // WebDAV helper protected WebDAVHelper m_davHelper; // Root node protected NodeRef m_rootNodeRef; // Repository path protected String m_strPath = null; /** * Default constructor */ public WebDAVMethod() { } /** * Set the request/response details * * @param req HttpServletRequest * @param resp HttpServletResponse * @param registry ServiceRegistry * @param rootNode NodeRef */ public void setDetails(HttpServletRequest req, HttpServletResponse resp, WebDAVHelper davHelper, NodeRef rootNode) { m_request = req; m_response = resp; m_davHelper = davHelper; m_rootNodeRef = rootNode; m_strPath = WebDAV.getRepositoryPath(req); } /** * Executes the method */ public void execute() throws WebDAVServerException { // Parse the HTTP headers parseRequestHeaders(); // Parse the HTTP body parseRequestBody(); TransactionWork executeWork = new TransactionWork() { public WebDAVServerException doWork() throws Exception { executeImpl(); return null; } }; try { // Execute the method TransactionService transactionService = getTransactionService(); TransactionUtil.executeInUserTransaction(transactionService, executeWork); } catch (AccessDeniedException e) { // Return a forbidden status throw new WebDAVServerException(HttpServletResponse.SC_UNAUTHORIZED, e); } catch (Throwable e) { Throwable cause = e.getCause(); if (cause instanceof WebDAVServerException) { throw (WebDAVServerException) cause; } else { // Convert error to a server error throw new WebDAVServerException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } } } /** * Access the content repository to satisfy the request and generates the appropriate WebDAV * response. * * @throws WebDAVServerException a general server exception * @throws Exception any unhandled exception */ protected abstract void executeImpl() throws WebDAVServerException, Exception; /** * Parses the given request body represented as an XML document and sets any necessary context * ready for execution. */ protected abstract void parseRequestBody() throws WebDAVServerException; /** * Parses the HTTP headers of the request and sets any necessary context ready for execution. */ protected abstract void parseRequestHeaders() throws WebDAVServerException; /** * Retrieves the request body as an XML document * * @return The body of the request as an XML document or null if there isn't a body */ protected Document getRequestBodyAsDocument() throws WebDAVServerException { Document body = null; if (m_request.getContentLength() > 0) { // TODO: Do we need to do anything for chunking support? try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); body = builder.parse(new InputSource(m_request.getReader())); } catch (ParserConfigurationException e) { throw new WebDAVServerException(HttpServletResponse.SC_BAD_REQUEST, e); } catch (SAXException e) { throw new WebDAVServerException(HttpServletResponse.SC_BAD_REQUEST, e); } catch (IOException e) { throw new WebDAVServerException(HttpServletResponse.SC_BAD_REQUEST, e); } } return body; } /** * Returns the lock token present in the If header * * @return The lock token present in the If header */ protected String parseIfHeader() throws WebDAVServerException { String strLockToken = null; String strIf = m_request.getHeader(WebDAV.HEADER_IF); if (logger.isDebugEnabled()) logger.debug("Parsing If header: " + strIf); if (strIf != null && strIf.length() > 0) { if (strIf.startsWith("(<")) { // Parse the tokens (only get the first one though) int idx = strIf.indexOf(">"); if (idx != -1) { try { strLockToken = strIf.substring(WebDAV.OPAQUE_LOCK_TOKEN.length() + 2, idx); } catch (IndexOutOfBoundsException e) { logger.warn("Failed to parse If header: " + strIf); } } else { throw new WebDAVServerException(HttpServletResponse.SC_BAD_REQUEST); } // Print a warning if there are other tokens detected if (strIf.length() > idx + 2) { logger.warn("The If header contained more than one lock token, only one is supported"); } } else if (strIf.startsWith("<")) { logger.warn("Tagged lists in the If header are not supported"); } else if (strIf.startsWith("([")) { logger.warn("ETags in the If header are not supported"); } } return strLockToken; } /** * Return the WebDAV protocol helper * * @return WebDAVHelper */ protected final WebDAVHelper getDAVHelper() { return m_davHelper; } /** * Return the service registry * * @return ServiceRegistry */ protected final ServiceRegistry getServiceRegistry() { return m_davHelper.getServiceRegistry(); } /** * Convenience method to return the transaction service * * @return TransactionService */ protected final TransactionService getTransactionService() { return m_davHelper.getServiceRegistry().getTransactionService(); } /** * Convenience method to return the node service * * @return NodeService */ protected final NodeService getNodeService() { return m_davHelper.getNodeService(); } /** * @return Returns the general file/folder manipulation service */ protected final FileFolderService getFileFolderService() { return m_davHelper.getFileFolderService(); } /** * Convenience method to return the content service * * @return ContentService */ protected final ContentService getContentService() { return m_davHelper.getServiceRegistry().getContentService(); } /** * Convenience method to return the mimetype service * * @return MimetypeService */ protected final MimetypeService getMimetypeService() { return m_davHelper.getMimetypeService(); } /** * Convenience method to return the lock service * * @return LockService */ protected final LockService getLockService() { return m_davHelper.getLockService(); } /** * Convenience method to return the authentication service * * @return AuthenticationService */ protected final AuthenticationService getAuthenticationService() { return m_davHelper.getAuthenticationService(); } /** * @return Returns the path of the servlet */ protected final String getServletPath() { return m_request.getServletPath(); } /** * Return the root node * * @return NodeRef */ protected final NodeRef getRootNodeRef() { return m_rootNodeRef; } /** * Return the relative path * * @return String */ protected final String getPath() { return m_strPath; } /** * Create an XML writer for the response * * @return XMLWriter * @exception IOException */ protected final XMLWriter createXMLWriter() throws IOException { // Check if debug output or XML pretty printing is enabled XMLWriter writer = null; if (XMLPrettyPrint == true || logger.isDebugEnabled()) { writer = new XMLWriter(m_response.getWriter(), OutputFormat.createPrettyPrint()); } else { writer = new XMLWriter(m_response.getWriter(), OutputFormat.createCompactFormat()); } // Return the writer return writer; } /** * Generates the lock discovery XML response * * @param xml XMLWriter * @param lockNode NodeRef */ protected void generateLockDiscoveryXML(XMLWriter xml, NodeRef lockNode) throws Exception { Attributes nullAttr= getDAVHelper().getNullAttributes(); if (lockNode != null) { // Get the lock details NodeService nodeService = getNodeService(); String owner = (String) nodeService.getProperty(lockNode, ContentModel.PROP_LOCK_OWNER); Date expiryDate = (Date) nodeService.getProperty(lockNode, ContentModel.PROP_EXPIRY_DATE); // Output the XML response xml.startElement(WebDAV.DAV_NS, WebDAV.XML_LOCK_DISCOVERY, WebDAV.XML_NS_LOCK_DISCOVERY, nullAttr); xml.startElement(WebDAV.DAV_NS, WebDAV.XML_ACTIVE_LOCK, WebDAV.XML_NS_ACTIVE_LOCK, nullAttr); xml.startElement(WebDAV.DAV_NS, WebDAV.XML_LOCK_TYPE, WebDAV.XML_NS_LOCK_TYPE, nullAttr); xml.write(DocumentHelper.createElement(WebDAV.XML_NS_WRITE)); xml.endElement(WebDAV.DAV_NS, WebDAV.XML_LOCK_TYPE, WebDAV.XML_NS_LOCK_TYPE); // NOTE: We only do exclusive lock tokens at the moment xml.startElement(WebDAV.DAV_NS, WebDAV.XML_LOCK_SCOPE, WebDAV.XML_NS_LOCK_SCOPE, nullAttr); xml.write(DocumentHelper.createElement(WebDAV.XML_NS_EXCLUSIVE)); xml.endElement(WebDAV.DAV_NS, WebDAV.XML_LOCK_SCOPE, WebDAV.XML_NS_LOCK_SCOPE); // NOTE: We only support one level of lock at the moment xml.startElement(WebDAV.DAV_NS, WebDAV.XML_DEPTH, WebDAV.XML_NS_DEPTH, nullAttr); xml.write(WebDAV.ZERO); xml.endElement(WebDAV.DAV_NS, WebDAV.XML_DEPTH, WebDAV.XML_NS_DEPTH); xml.startElement(WebDAV.DAV_NS, WebDAV.XML_OWNER, WebDAV.XML_NS_OWNER, nullAttr); xml.write(owner); xml.endElement(WebDAV.DAV_NS, WebDAV.XML_OWNER, WebDAV.XML_NS_OWNER); xml.startElement(WebDAV.DAV_NS, WebDAV.XML_TIMEOUT, WebDAV.XML_NS_TIMEOUT, nullAttr); // Output the expiry time String strTimeout = WebDAV.INFINITE; if (expiryDate != null) { long timeoutRemaining = (expiryDate.getTime() - System.currentTimeMillis())/1000L; strTimeout = WebDAV.SECOND + timeoutRemaining; } xml.write(strTimeout); xml.endElement(WebDAV.DAV_NS, WebDAV.XML_TIMEOUT, WebDAV.XML_NS_TIMEOUT); xml.startElement(WebDAV.DAV_NS, WebDAV.XML_LOCK_TOKEN, WebDAV.XML_NS_LOCK_TOKEN, nullAttr); xml.startElement(WebDAV.DAV_NS, WebDAV.XML_HREF, WebDAV.XML_NS_HREF, nullAttr); xml.write(WebDAV.makeLockToken(lockNode, owner)); xml.endElement(WebDAV.DAV_NS, WebDAV.XML_HREF, WebDAV.XML_NS_HREF); xml.endElement(WebDAV.DAV_NS, WebDAV.XML_LOCK_TOKEN, WebDAV.XML_NS_LOCK_TOKEN); xml.endElement(WebDAV.DAV_NS, WebDAV.XML_ACTIVE_LOCK, WebDAV.XML_NS_ACTIVE_LOCK); xml.endElement(WebDAV.DAV_NS, WebDAV.XML_LOCK_DISCOVERY, WebDAV.XML_NS_LOCK_DISCOVERY); } } /** * Generates a list of namespace declarations for the response */ protected String generateNamespaceDeclarations(HashMap nameSpaces) { StringBuilder ns = new StringBuilder(); ns.append(" "); ns.append(WebDAV.XML_NS); ns.append(":"); ns.append(WebDAV.DAV_NS); ns.append("=\""); ns.append(WebDAV.DEFAULT_NAMESPACE_URI); ns.append("\""); // Add additional namespaces if ( nameSpaces != null) { Iterator namespaceList = nameSpaces.keySet().iterator(); while (namespaceList.hasNext()) { String strNamespaceUri = namespaceList.next(); String strNamespaceName = nameSpaces.get(strNamespaceUri); ns.append(" ").append(WebDAV.XML_NS).append(":").append(strNamespaceName).append("=\""); ns.append(strNamespaceUri).append("\" "); } } return ns.toString(); } }