/* * 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.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.StringTokenizer; import org.alfresco.model.ContentModel; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.lock.LockService; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileNotFoundException; import org.alfresco.service.cmr.repository.CopyService; 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.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.AuthenticationService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.util.EqualsHelper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.xml.sax.helpers.AttributesImpl; /** * WebDAV Protocol Helper Class * *

Provides helper methods for repository access using the WebDAV protocol. * * @author GKSpencer */ public class WebDAVHelper { // Constants // Path seperator public static final String PathSeperator = "/"; public static final char PathSeperatorChar = '/'; // Logging private static Log logger = LogFactory.getLog("org.alfresco.protocol.webdav"); // Service registry private ServiceRegistry m_serviceRegistry; // Services private NodeService m_nodeService; private FileFolderService m_fileFolderService; private SearchService m_searchService; private NamespaceService m_namespaceService; private DictionaryService m_dictionaryService; private MimetypeService m_mimetypeService; private LockService m_lockService; private AuthenticationService m_authService; // Empty XML attribute list private AttributesImpl m_nullAttribs = new AttributesImpl(); /** * Class constructor * * @param serviceRegistry ServiceRegistry * @param authService AuthenticationService */ protected WebDAVHelper(ServiceRegistry serviceRegistry, AuthenticationService authService) { m_serviceRegistry = serviceRegistry; m_nodeService = m_serviceRegistry.getNodeService(); m_fileFolderService = m_serviceRegistry.getFileFolderService(); m_searchService = m_serviceRegistry.getSearchService(); m_namespaceService = m_serviceRegistry.getNamespaceService(); m_dictionaryService = m_serviceRegistry.getDictionaryService(); m_mimetypeService = m_serviceRegistry.getMimetypeService(); m_lockService = m_serviceRegistry.getLockService(); m_authService = authService; } /** * Return the authentication service * * @return AuthenticationService */ public final AuthenticationService getAuthenticationService() { return m_authService; } /** * Return the service registry * * @return ServiceRegistry */ public final ServiceRegistry getServiceRegistry() { return m_serviceRegistry; } /** * Return the node service * * @return NodeService */ public final NodeService getNodeService() { return m_nodeService; } public FileFolderService getFileFolderService() { return m_fileFolderService; } /** * Return the search service * * @return SearchService */ public final SearchService getSearchService() { return m_searchService; } /** * Return the namespace service * * @return NamespaceService */ public final NamespaceService getNamespaceService() { return m_namespaceService; } /** * Return the dictionary service * * @return DictionaryService */ public final DictionaryService getDictionaryService() { return m_dictionaryService; } /** * Return the mimetype service * * @return MimetypeService */ public final MimetypeService getMimetypeService() { return m_mimetypeService; } /** * Return the lock service * * @return LockService */ public final LockService getLockService() { return m_lockService; } /** * Return the copy service * * @return CopyService */ public final CopyService getCopyService() { return getServiceRegistry().getCopyService(); } /** * Split the path into seperate directory path and file name strings. * If the path is not empty, then there will always be an entry for the filename * * @param path Full path string. * @return Returns a String[2] with the folder path and file path. */ public final String[] splitPath(String path) { if (path == null) throw new IllegalArgumentException("path may not be null"); // Create an array of strings to hold the path and file name strings String[] pathStr = new String[] {"", ""}; // Check if the path has a trailing seperator, if so then there is no file name. int pos = path.lastIndexOf(PathSeperatorChar); if (pos == -1 || pos == (path.length() - 1)) { // Set the path string in the returned string array pathStr[1] = path; } else { pathStr[0] = path.substring(0, pos); pathStr[1] = path.substring(pos + 1); } // Return the path strings return pathStr; } /** * Split the path into all the component directories and filename * * @param path String * @return String[] */ public final List splitAllPaths(String path) { if (path == null || path.length() == 0) { return Collections.emptyList(); } // split the path StringTokenizer token = new StringTokenizer(path, PathSeperator); List results = new ArrayList(10); while (token.hasMoreTokens()) { results.add(token.nextToken()); } return results; } /** * Get the file info for the given paths * * @param rootNodeRef the acting webdav root * @param path the path to search for * @param servletPath the base servlet path, which may be null or empty * @return Return the file info for the path * @throws FileNotFoundException if the path doesn't refer to a valid node */ public final FileInfo getNodeForPath(NodeRef rootNodeRef, String path, String servletPath) throws FileNotFoundException { if (rootNodeRef == null) { throw new IllegalArgumentException("Root node may not be null"); } else if (path == null) { throw new IllegalArgumentException("Path may not be null"); } FileFolderService fileFolderService = getFileFolderService(); // Check for the root path if ( path.length() == 0 || path.equals(PathSeperator) || EqualsHelper.nullSafeEquals(path, servletPath)) { return fileFolderService.getFileInfo(rootNodeRef); } // remove the servlet path from the path if (servletPath != null && servletPath.length() > 0 && path.startsWith(servletPath)) { // Strip the servlet path from the relative path path = path.substring(servletPath.length()); } // split the paths up List splitPath = splitAllPaths(path); // find it FileInfo fileInfo = m_fileFolderService.resolveNamePath(rootNodeRef, splitPath); // done if (logger.isDebugEnabled()) { logger.debug("Fetched node for path: \n" + " root: " + rootNodeRef + "\n" + " path: " + path + "\n" + " servlet path: " + servletPath + "\n" + " result: " + fileInfo); } return fileInfo; } public final FileInfo getParentNodeForPath(NodeRef rootNodeRef, String path, String servletPath) throws FileNotFoundException { if (rootNodeRef == null) { throw new IllegalArgumentException("Root node may not be null"); } else if (path == null) { throw new IllegalArgumentException("Path may not be null"); } // shorten the path String[] paths = splitPath(path); return getNodeForPath(rootNodeRef, paths[0], servletPath); } /** * Return the relative path for the node walking back to the specified root node * * @param rootNodeRef the root below which the path will be valid * @param nodeRef the node's path to get * @return Returns string of form /A/B/C where C represents the from node and */ public final String getPathFromNode(NodeRef rootNodeRef, NodeRef nodeRef) throws FileNotFoundException { // Check if the nodes are valid, or equal if (rootNodeRef == null || nodeRef == null) throw new IllegalArgumentException("Invalid node(s) in getPathFromNode call"); // short cut if the path node is the root node if (rootNodeRef.equals(nodeRef)) return ""; FileFolderService fileFolderService = getFileFolderService(); // get the path elements List pathInfos = fileFolderService.getNamePath(rootNodeRef, nodeRef); // build the path string StringBuilder sb = new StringBuilder(pathInfos.size() * 20); for (FileInfo fileInfo : pathInfos) { sb.append(WebDAVHelper.PathSeperatorChar); sb.append(fileInfo.getName()); } // done if (logger.isDebugEnabled()) { logger.debug("Build name path for node: \n" + " root: " + rootNodeRef + "\n" + " target: " + nodeRef + "\n" + " path: " + sb); } return sb.toString(); } /** * Make an ETag value for a node using the GUID and modify date/time * * @param node NodeRef * @return String */ public final String makeETag(NodeRef node) { // Get the modify date/time property for the node StringBuilder etag = new StringBuilder(); makeETagString(node, etag); return etag.toString(); } /** * Make an ETag value for a node using the GUID and modify date/time * * @param node NodeRef * @return String */ public final String makeQuotedETag(NodeRef node) { StringBuilder etag = new StringBuilder(); etag.append("\""); makeETagString(node, etag); etag.append("\""); return etag.toString(); } /** * Make an ETag value for a node using the GUID and modify date/time * * @param node NodeRef * @param str StringBuilder */ protected final void makeETagString(NodeRef node, StringBuilder etag) { // Get the modify date/time property for the node Object modVal = getNodeService().getProperty(node, ContentModel.PROP_MODIFIED); etag.append(node.getId()); if ( modVal != null) { etag.append("_"); etag.append(DefaultTypeConverter.INSTANCE.longValue(modVal)); } } /** * Return the null XML attribute list * * @return AttributesImpl */ public final AttributesImpl getNullAttributes() { return m_nullAttribs; } /** * Encodes the given string to valid URL format * * @param s the String to convert */ public final static String encodeURL(String s) { try { return replace(URLEncoder.encode(s, "UTF-8"), "+", "%20"); } catch (UnsupportedEncodingException err) { throw new RuntimeException(err); } } /** * Replace one string instance with another within the specified string * * @param str * @param repl * @param with * * @return replaced string */ public static String replace(String str, String repl, String with) { int lastindex = 0; int pos = str.indexOf(repl); // If no replacement needed, return the original string // and save StringBuffer allocation/char copying if (pos < 0) { return str; } int len = repl.length(); int lendiff = with.length() - repl.length(); StringBuilder out = new StringBuilder((lendiff <= 0) ? str.length() : (str.length() + (lendiff << 3))); for (; pos >= 0; pos = str.indexOf(repl, lastindex = pos + len)) { out.append(str.substring(lastindex, pos)).append(with); } return out.append(str.substring(lastindex, str.length())).toString(); } /** * Encodes the given string to valid HTML format * * @param string the String to convert */ public final static String encodeHTML(String string) { if (string == null) { return ""; } StringBuilder sb = null; //create on demand String enc; char c; for (int i = 0; i < string.length(); i++) { enc = null; c = string.charAt(i); switch (c) { case '"': enc = """; break; //" case '&': enc = "&"; break; //& case '<': enc = "<"; break; //< case '>': enc = ">"; break; //> //german umlauts case '\u00E4' : enc = "ä"; break; case '\u00C4' : enc = "Ä"; break; case '\u00F6' : enc = "ö"; break; case '\u00D6' : enc = "Ö"; break; case '\u00FC' : enc = "ü"; break; case '\u00DC' : enc = "Ü"; break; case '\u00DF' : enc = "ß"; break; //misc //case 0x80: enc = "€"; break; sometimes euro symbol is ascii 128, should we suport it? case '\u20AC': enc = "€"; break; case '\u00AB': enc = "«"; break; case '\u00BB': enc = "»"; break; case '\u00A0': enc = " "; break; default: if (((int)c) >= 0x80) { //encode all non basic latin characters enc = "&#" + ((int)c) + ";"; } break; } if (enc != null) { if (sb == null) { String soFar = string.substring(0, i); sb = new StringBuilder(i + 8); sb.append(soFar); } sb.append(enc); } else { if (sb != null) { sb.append(c); } } } if (sb == null) { return string; } else { return sb.toString(); } } }