Files
alfresco-community-repo/source/java/org/alfresco/repo/webdav/WebDAVHelper.java
Dave Ward 11ee3f8511 Merged V4.1-BUG-FIX to HEAD
41035: Fix for ALF-15225 - qt.length not performing as expected in search.lib.js
   41047: RUSSIAN: Translation updates based on EN r40961
   41049: GERMAN: Translation updates based on EN r41033. Fixes ALF-15749 and ALF-15720.
   41050: SPANISH: Translation updates based on EN r41033. Fixes ALF-15749 and ALF-15720.
   41051: FRENCH: Translation updates based on EN r41033. Fixes ALF-15749 and ALF-15720.
   41052: ITALIAN: Translation updates based on EN r41033. Fixes ALF-15749 and ALF-15720.
   41053: JAPANESE: Translation updates based on EN r41033. Fixes ALF-15749 and ALF-15720.
   41054: DUTCH: Translation updates based on EN r41033. Fixes ALF-15749 and ALF-15720.
   41055: RUSSIAN: Translation updates based on EN r41033. Fixes ALF-15749 and ALF-15720.
   41061: ALF-11214 - IMAP subsystem is not successfully restarted after incorrect modification of IMAP properties via Admin Console
   41063: RUSSIAN: Translation updates based on EN r41033 (encoding fixes)
   41064: CHINESE: Translation updates based on EN r41033
   41073: ALF-15760: Merged V4.0 to V4.1-BUG-FIX (another lost meta-inf revision)
      34416: ALF-12992: Updated weblogic DD for SOLR
   41074: ALF-15419 / ALF-14438: Merged V3.4-BUG-FIX to V4.1-BUG-FIX (PARTIAL)
      37373: Merged PATCHES/V3.4.6 to V3.4-BUG-FIX
         36821: ALF-13827 / ALF-14402: Make replicated caches recover from temporary comms failures by flushing when a change in peers is detected
         - We do not flush caches who replicate via copy (e.g. tickets cache) as these may not be recoverable
         37122: ALF-13919 / ALF-14403: Merged DEV to PATCHES/V3.4.6
         - Rework of Dmitry's implementation
         - Uses dynamic HQL query to retrieve JBPM workflow instances by specified query criteria
         - WorkflowInstancesGet web script no longer has to iterate over every workflow instance in the database!
         - DB index added to enable efficient querying by string variable
         - Hibernate tastic!
         37188: ALF-13919 / ALF-14403: Worked around HQL polymorphism issues by using explicit variable subclass names in from clause
         37204: ALF-13919 / ALF-14403: Fix to date range handling by Dmitry
   41077: ALF-10730: Fixed object finder drop-down tree to show parent icon type when icon type not available
   41089: ALF-13998: 'No items' error is highlighted in red, even that is not sever error.
   41109: Part fix for ALF-11297 Disabled test: test-system-build-test has been removed from the test target dependency list
   41118: Fixes: ALF-15765 and related issue ALF-15476: 
      - Corrects merge of r34405 and r40159
      - Adds people api to cloud proxy config
      - Cloud/Global Folder picker sites list now universally shows only those sites the user is a member of.
   41119: ALF-15419 / ALF-14438: Merged DEV to V4.1-BUG-FIX
      41117: ALF-15419 : CLONE Remove JBPM indexes present from upgrades
         The schema reference files were updated to contain JBPM_VARIABLEINSTANCE.IDX_VARINST_STRVAL index.
   41130: ALF-15590: FSTR transfer of custom content type with parent cm:content fails
   - Fix by Dmitry
   41131: ALF-15714: Bitrock Uninstaller: Uninstaller doesn't remove all files and folders in *nix
   - Fix provided by Bitrock
   41132: Fix for ALF-14388 - Edit Online option is not supported for '.docm', 'dotm', '.xlsm' files
    - corrected mimetypes to be lower-case
   41150: ALF-13287 Added the handling of UTC represented by "Z" within comparator
   41154: Check for authentication errors when validating a ticket, if an error occurs re-authenticate. ALF-15394
   41155: ALF-15569: User with '@' symbol in username cannot invite
   - note: since Ent 4.0.2(+) by default MT is pre-configured but not enabled
   41173: Merged V4.1 to V4.1-BUG-FIX
      41121: Merged BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC to BRANCHES/V4.1:
         41003: CloudSync: ALF-15734 - force unsync (of last SSMN) on target causes repeating pull errors to appear in both logs
         41026: CloudSync: ALF-15734 - force unsync (of last SSMN) on target causes repeating pull errors to appear in both logs
         41039: CloudSync: ALF-15734 - force unsync (of last SSMN) on target causes repeating pull errors to appear in both logs
         41086: CloudSync: ALF-15734 - force unsync (of last SSMN) on target causes repeating pull errors to appear in both logs
      41123: Merged BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC to BRANCHES/V4.1:
         41115: CloudSync: ALF-15734 - force unsync (of last SSMN) on target causes repeating pull errors to appear in both logs
   41176: Attempt to debug unit test failure
   41181: Store leak in AVMServiceTest.test_ETWOTWO_570() causing unit test failure
   41184: ALF-15610: Copy Thai analyzer settings to its many SOLR locations
   41194: ALF-11297: re-enable system build tests
   41195: ALF-11297 ALF-15807: update activities system build tests after correction of ALF-4832
   41201: Fix for ALF-15767 Group query using cm:authorityName
   41202: Additional unit tests related to ALF-15731  TYPE:"..." queries no longer work for Lucene on 4.X
   41203: Part 1 for ALF-15811 SOLR query increases DocBitSet inefficiently
   - check it makes any difference
   41204: Merged BRANCHES/DEV/BELARUS/V4.1-BUG-FIX-2012_08_15 to BRANCHES/DEV/V4.1-BUG-FIX: (note: merging as-is ... refactor + unit test fix to follow in next commit)
      40926: ALF-12586: Admin Console shows usage as zero - if user is deleted and then re-created (eg. re-synchronized via LDAP or manually)
      40974: ALF-12586: Admin Console shows usage as zero - if user is deleted and then re-created (eg. re-synchronized via LDAP or manually)
   41205: ALF-12586: Admin Console shows usage as zero - if user is deleted and then re-created (eg. re-synchronized via LDAP or manually)
   - review and refactor the proposed/merged fix (see previous commit)
   - fix unit test so that it accounts for previous content (as per the original issue)
   - also: add missing test to suite and fix that test to work with the new fix (ie. cleanup previous content, else need to account for it)
   41210: Part 2:  ALF-14861 SOLR to scale for non-admin users in 100k sites and a subgroup of each of 1000 independent groupings with 1000 subgroups
   - do not expand authorities for thoses with the ADMINISTRATOR_ROLE as they can read all anyway
   41216: ALF-11297: system build tests need a database cleanup before running
   41222: ALF-15740, ALF-14744: Update rule firing broken for content created in Explorer
   - Old code lurking around that used to use the inline editable aspect to detect events handled by the CreateNodeRuleTrigger was removed from OnContentUpdateRuleTrigger and replaced with a check for ASPECT_NO_CONTENT
   41223: Added missing swf.languagedir setting to enterprise alfresco-global.properties
   41230: GERMAN: Translation updates based on EN rev41099.
   41232: SPANISH: Translation updates based on EN rev41099.
   41233: FRENCH: Translation updates based on EN rev41099.
   41234: ITALIAN: Translation updates based on EN rev41099.
   41235: JAPANESE: Translation updates based on EN rev41099.
   41236: DUTCH: Translation updates based on EN rev41099.
   41237: RUSSIAN: Translation updates based on EN rev41099.
   41239: CHINESE: Translation updates based on EN rev41099.
   41254: ALF-15628: Avoid edit online (SPP, WRITE_LOCK) clashing with edit offline (CheckOutCheckInService, READ_ONLY_LOCK)
   - Rationalization of work by Alex Malinovsky
   - WebDAVMethod.checkNode() now properly checks whether nodes without WebDAV lock info are writeable
   - CheckOutCheckInService won't allow checkout of a node with an existing WRITE_LOCK by the same user - they must unlock first
   - Propagation of correct status codes
   41264: ALF-15628: Fix CheckOutCheckInService test failures
   41265: ALF-15699: Reverse merged the following, thus downgrading us back to swftools 0.9.1
      40208: ALF-12831: Upgrade to swftools 0.9.2
   41266: Rush'n in some translation updates from Gloria
   41267: ALF-15628: Fix compilation problem
   41269: Merged V3.4-BUG-FIX to V4.1-BUG-FIX (RECORD ONLY)
      41224: ALF-14856: Merged V4.1-BUG-FIX to V3.4-BUG-FIX
      41268: ALF-15459: Merged PATCHES/V4.0.2 to V3.4-BUG-FIX
         Merged V4.1-BUG-FIX to V3.4-BUG-FIX
   41274: ALF-15608: Merged V3.4-BUG-FIX to V4.1-BUG-FIX
      41272: ALF-15567: Allow links to be followed through WebDAV on port 80 using basic auth on XP
   41277: ALF-12586: Admin Console shows usage as zero - if user is deleted and then re-created (eg. re-synchronized via LDAP or manually) 
   - fix test fallout after merge/fix (note: failed for PostgreSQL but not MySQL - although fix was not DB-specific)
   41278: ALF-15840 Error logged when "No thumbnail present in file" even though this is normal 
   41284: ALF-14875: Serialize direct permissions after inherited permissions so that they take precedence in any lookups in permission dialogs
   41290: JAPANESE: Translation updates based on EN r41099 Fixes: ALF-14565
   41296: ALF-15251	CIFS: Checked out document is not marked as locked in CIFS
   41299: ALF-15714: Bitrock Uninstaller: Uninstaller doesn't remove all files and folders in *nix
   - Additional fix provided by Bitrock
   41303: Fix for ALF-15799 Under high concurrency load balanced Solr throws an Antlr related NPE
   - do not skip IO Exceptions
   41306: Incremented version revision for 4.1.2
   41309: ALF-15827: Added FORMACTION, FORMMETHOD and ACTION  HTML attributes to grey list to close security hole (updated Surf libs r1136)
   41318: ALF-15857: Lucene FTS indexer opens streams to all documents to be indexed in a transaction simultaneously
   - Now stream opening is delayed until the point where the document is being written to the index
   41322: Fix for ALF-15858 SOLR ACL tracking can stall or miss acls during tracking
   41323: Chemistry client java to create test data for ALF-15858, ALF-15782, CLOUD-596, ALF-15753 etc
   41326: ALF-15234: IE 8 or IE9 Download .pps as .ppt 
      -Switched the order of the filename headers to better support non-conforming browsers (rfc 5987).
   41330: ALF-14875: Reversed r41284 because it didn't solve the problem in the UI. Kev reviewing.
   41332: ALF-10688: Can't deactivate an account when alfrescoNtlm follows another authentication subsystem in the authentication chain
   - Now, if a account is known to be 'mutable' then the enabled flag is read from the mutable authentication service
   41337: Merged V3.4-BUG-FIX (3.4.11) to V4.1-BUG-FIX (4.1.2)
      41336: TransformerDebug: Use debug rather than trace when there are transformers but they are all unavailable.
   41339: ALF-15840 Error logged when "No thumbnail present in file" even though this is normal
      - Found some more cases where this is logged as an ERROR
   41342: ALF-11087 (Missing icon file: components\images\filetypes\generic-tag-32.png)
   41344: ALF-15863 (* search values): Merged HEAD to V4.1-BUG-FIX (4.1.2)
      40849: ALF-12839 "Share - Inconsistency in adding a user or a group into a group" part 2
      - Making the users console stop "*" searches, just like the groups console when the min search length is set to larger than 0.
   41346: ALF-15237 - REST API Group children lists username for fullName and displayName
   41350: Merge V3.4-BUG-FIX to V4.1-BUG-FIX:
   41065: Disconnect existing CIFS sessions from the same client when a virtual circuit zero session is opened. ALF-13815
   41280: Moved session cleanup config into the base authenticator, added support to passthru/base authentication. ALF-13815
   41351: Ported database filesystem changes to fix session disconnect, from V3.4.
   41352: Merge V3.4-BUG-FIX to V4.1-BUG-FIX:
   41067: Added session disconnect support to the Alfresco CIFS authenticator. ALF-13815.
   41281: Added session cleanup support to passthru authenticator, session cleanup config moved to base class. ALF-13815 
   41353: Update svn:mergeinfo
   41355: Fix for ALF-15869 - "Site Content" dashlet shows all documents from all the sites in Alfresco Share
    - mistakenly did a record-only merge of this from 4.1->4.1.1
   41363: Fix for ALF-14875 - Manage permissions shows the permission 'No privileges' for All Other Users
    - reworked the permissions dialog and permissions panels to correctly handle multiple permissions on a special permissions group such as GROUP_EVERYONE
    - now correctly gets/sets permissions for GROUP_EVERYONE
    - this also fixes ALF-12014 - in that it allows custom SiteXYZ permissions to work correctly again also (will need manual backport for 3.4.X though)
    - removed hacks related to previous attempts to fix the above issue
    - added lots of comments around relevant sections to add in future refactoring or understanding
   41371: Merged BRANCHES/DEV/V3.4-BUG-FIX to BRANCHES/DEV/V4.1-BUG-FIX (RECORD ONLY)
      41370: Fix for ALF-12014 - Share - Custom role causes incorrect display of permissions
   Manual merge of changes to 4.1.2
   41399: ALF-13438: java.lang.OutOfMemoryError appears in alfresco log when trying to create few thousands of sites consistently. 
   - The v4.0 parent assocs cache used to store every verion of every node's parent assocs, so as we added a node to 60,000 sites, we retained all previous versions of the user's parent association map
   - After creating 10,000 sites, the cache size was about 4GB, containing about 2 million parents
   - Now we use a specialized class that allows an upper limit to be set on the total number of cached parents as well as children.
   - Because the cache is keyed by node transaction ID, the cache can be non-clustered and non-transactional
   - Once the average number of parents is more than 8, the cache will drop its oldest entries
   - ParentAssocsInfo also now uses a compact TreeMap instead of a HashMap
   41401: ALF-13438: Small correction
   41406: ALF-13438: Fix failing unit tests
   - Removed unused node.parentAssocsSharedCache and corrected node.childByNameCache not to reference it!
   - Because parent assocs are cached by transaction ID, we must always invalidate them on an in-transaction version increment
   41409: Logging of unexepected errors on FTP
   41411: Fix possible FTP data session leak if client mixes PORT and PASV commands. ALF-15126
   41412: ALF-15845 : Clone for Hotfix: Word document on Windows via CIFS becomes locked (Read Only) when network drops temporarily
   41415: Fixes: ALF-15649: Removes country locale from files with it hard coded.
   41419: ALF-14599: Removed ftp.ipv6.enabled from enterprise overlay and bundles
   41426: ALF-15845 Clone for Hotfix: Word document on Windows via CIFS becomes locked (Read Only) when network drops temporarily
     Roll back changes to DiskDriver interface in favour of hacking NetworkFile.
   41440: JAPANESE: Translation update based on EN r41099
   41446: ALF-13091: Remove unecessary bean post processors from sub ssytem context and remove CXF's Jsr250BeanPostProcessor.
   41458: RUSSIAN: Further translation updates following linguistic review.
   41459: ALF-15897: Revert revision 41446, an attempted fix for ALF-13091
   41487: Fix for ALF-15910 SOLR - Add index warming and filter pointless entries from the filter cache
   Fix for ALF-15851
   Too many live instances of SolrIndexSearcher at one time resulting in OOM - Alfresco 4.1.1 - build 151
   41506: Merged DEV to V4.1-BUG-FIX
      41505: ALF-15879: PostgreSQL: upgrade 2.2.8 (577) -> 3.4.10 (703) -> 4.1.1 (159) failed.
             - Make dropping "store_id" index and "alf_node_store_id_key" constraint optional in 4.1.1 upgrade script
               because clean 3.4 has "store_id" index and doesn't have "alf_node_store_id_key" constraint,
               but 3.4 upgraded from 2.2  has "alf_node_store_id_key" constraint and doesn't have "store_id" index.
   41531: Fixed ALF-15687, so that any user (except for Admin) won’t be able to retrieve any other user’s preferences via REST API. Also, updated the preferences controllers for the Post and Delete.
   41539: ALF-15899: Inbound email does not support multiple recipient folders
   - Fix by Dmitry Vaserin
   41540: Merged V3.4-BUG-FIX to V4.1-BUG-FIX
      40794: Merged DEV to V3.4-BUG-FIX
         40793: ALF-13752 Saving Word (mac 2011) documents via CIFS into a folder with Versionable rules on Mac OS X Lion (Fix for 3.4)
            In ContentDiskDriver.renameFile() was added a check whether a node in the archive.
      40806: Fix for ALF-9787 - Hiding sites in Share with permissionsDefinitions.xml [creates a permissions error in the blog portion of the site]
      40922: Merged DEV to V3.4-BUG-FIX (reviewed by Frederik)
         40488: ALF-13357 : Empty outcome when a timer is invoked
            A check for transitionName was added to AlfrescoTimer to support custom transitions.
      40940: ALF-15696: Remove svnkit.jar - makes the build fail if the version of installed command line svn is 1.7. Using commandline binding for <svn> Ant task to be consistent with other parts of the build.
      41066: Various fixes to the database filesystem for session disconnect cleanup.
      41068: Updates to the Alfresco filesystem for session cleanup. ALF-13815
      41301: Merged PATCHES/V3.4.9 to V3.4-BUG-FIX
         40966: ALF-15846 / ALF-15709: OOM on cascading reindex
         - Avoid buffering of all the affected PATH documents in memory - used the set of 'visited' paths to delay generation to the final flush.
         41044: ALF-15847 / ALF-15748: Lucene indexer can make sub-optimal cascade reindex decisions during an LDAP sync.
         - When a user in 6 groups was removed from a massive group, the massive group was getting cascade reindexed rather than the user
         - Logic adjusted as follows:
            For nodes with 5 or less parents, we always cascade reindex the child node. For nodes with more than 5 parents, we cascade reindex the parent node if it has less children than the child has parents.
      41395: ALF-15715: Unable to edit properties whilst transformation in progress
         - Delayed all changes that would potentially lock the parent node row while the thumbnail is generating.
         - Reorganized RenditionContext to lazily instantiate its destination node.
         - Changed render destination to use temporary ContentWriter until render is complete.
      41396: ALF-15715: Fix for failing unit tests.
      41413: Fix for issue where user calendar remote api was generating invalid date searches for user dashlet calendar.
      41509: Merged DEV to V3.4-BUG-FIX
         41507: ALF-12833: Issues installing Alfresco on WebSphere when the server doesn't have internet access
            Context-param which Sets "http://apache.org/xml/features/nonvalidating/load-external-dtd" feature on the SAXParser to false if this parameter is false 
      41510: ALF-15171: After addition of a secondary parent association to a container, not all index paths were being regenerated due to a logic error
      41512: ALF-15919: Merged PATCHES/V3.4.10 to V3.4-BUG-FIX
         41091: ALF-15723:  Merged DEV to PATCHES/V3.4.10
            26579: Switch the transformer to use Tika


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@41543 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2012-09-12 19:10:54 +00:00

977 lines
32 KiB
Java

/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.webdav;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.NetworkInterface;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.alfresco.jlan.util.IPAddress;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.lock.LockUtils;
import org.alfresco.repo.tenant.TenantService;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.dictionary.DictionaryService;
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.cmr.security.PermissionService;
import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.EqualsHelper;
import org.alfresco.util.Pair;
import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.surf.util.URLDecoder;
import org.springframework.extensions.surf.util.URLEncoder;
import org.springframework.util.StringUtils;
import org.xml.sax.helpers.AttributesImpl;
/**
* WebDAV Protocol Helper Class
*
* <p>Provides helper methods for repository access using the WebDAV protocol.
*
* @author GKSpencer
*/
public class WebDAVHelper
{
// Constants
private static final String HTTPS_SCHEME = "https://";
private static final String HTTP_SCHEME = "http://";
// Path seperator
public static final String PathSeperator = "/";
public static final char PathSeperatorChar = '/';
public static final String EMPTY_SITE_ID = "";
// Logging
private static Log logger = LogFactory.getLog("org.alfresco.webdav.protocol");
// 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 WebDAVLockService m_lockService;
private ActionService m_actionService;
private AuthenticationService m_authService;
private PermissionService m_permissionService;
private TenantService m_tenantService;
// Empty XML attribute list
private AttributesImpl m_nullAttribs = new AttributesImpl();
private String m_urlPathPrefix;
/**
* Class constructor
*/
protected WebDAVHelper(String urlPathPrefix, ServiceRegistry serviceRegistry, AuthenticationService authService, TenantService tenantService)
{
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 = (WebDAVLockService)m_serviceRegistry.getService(QName.createQName(NamespaceService.ALFRESCO_URI, WebDAVLockService.BEAN_NAME));
m_actionService = m_serviceRegistry.getActionService();
m_permissionService = m_serviceRegistry.getPermissionService();
m_tenantService = tenantService;
m_authService = authService;
m_urlPathPrefix = urlPathPrefix;
}
/**
* @return Return the authentication service
*/
public final AuthenticationService getAuthenticationService()
{
return m_authService;
}
/**
* @return Return the service registry
*/
public final ServiceRegistry getServiceRegistry()
{
return m_serviceRegistry;
}
/**
* @return Return the node service
*/
public final NodeService getNodeService()
{
return m_nodeService;
}
public FileFolderService getFileFolderService()
{
return m_fileFolderService;
}
/**
* @return Return the search service
*/
public final SearchService getSearchService()
{
return m_searchService;
}
/**
* @return Return the namespace service
*/
public final NamespaceService getNamespaceService()
{
return m_namespaceService;
}
/**
* @return Return the dictionary service
*/
public final DictionaryService getDictionaryService()
{
return m_dictionaryService;
}
/**
* @return Return the mimetype service
*/
public final MimetypeService getMimetypeService()
{
return m_mimetypeService;
}
/**
* @return Return the lock service
*/
public final WebDAVLockService getLockService()
{
return m_lockService;
}
/**
* @return Return the action service
*/
public final ActionService getActionService()
{
return m_actionService;
}
/**
*
* @return Return the permission service
*/
public final PermissionService getPermissionService()
{
return m_permissionService;
}
/**
* Retrieve the {@link TenantService} held by the helper.
*
* @return TenantService
*/
public TenantService getTenantService()
{
return m_tenantService;
}
/**
* @return Return the copy service
*/
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 the string to split
* @return an array of all the path components
*/
public final List<String> splitAllPaths(String path)
{
if (path == null || path.length() == 0)
{
return Collections.emptyList();
}
// split the path
StringTokenizer token = new StringTokenizer(path, PathSeperator);
List<String> results = new ArrayList<String>(10);
while (token.hasMoreTokens())
{
results.add(token.nextToken());
}
return results;
}
public String getURLForPath(HttpServletRequest request, String path, boolean isCollection)
{
return getURLForPath(request, path, isCollection, null);
}
public String getURLForPath(HttpServletRequest request, String path, boolean isCollection, String userAgent)
{
String urlPathPrefix = getUrlPathPrefix(request);
StringBuilder urlStr = new StringBuilder(urlPathPrefix);
if (path.equals(WebDAV.RootPath) == false)
{
// split the path and URL encode each path element
for (StringTokenizer t = new StringTokenizer(path, PathSeperator); t.hasMoreTokens(); /**/)
{
urlStr.append( WebDAVHelper.encodeURL(t.nextToken(), userAgent) );
if (t.hasMoreTokens())
{
urlStr.append(PathSeperator);
}
}
}
// If the URL is to a collection add a trailing slash
if (isCollection && urlStr.charAt( urlStr.length() - 1) != PathSeperatorChar)
{
urlStr.append( PathSeperator);
}
logger.debug("getURLForPath() path:" + path + " => url:" + urlStr);
// Return the URL string
return urlStr.toString();
}
/**
* 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, assuming it hasn't already been done
if (servletPath != null && servletPath.length() > 0)
{
// Need to ensure we don't strip /alfresco from a site of /alfresco_name/
String comparePath = servletPath + "/";
if (path.startsWith(comparePath))
{
// Strip the servlet path from the relative path
path = path.substring(servletPath.length());
}
}
// split the paths up
List<String> 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 <b>/A/B/C</b> 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<FileInfo> 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();
}
public List<FileInfo> getChildren(FileInfo fileInfo) throws WebDAVServerException
{
return m_fileFolderService.list(fileInfo.getNodeRef());
}
/**
* Make an ETag value for a node using the GUID and modify date/time
*/
public final String makeETag(FileInfo nodeInfo)
{
// Get the modify date/time property for the node
StringBuilder etag = new StringBuilder();
makeETagString(nodeInfo, etag);
return etag.toString();
}
/**
* Make an ETag value for a node using the GUID and modify date/time
*/
public final String makeQuotedETag(FileInfo nodeInfo)
{
StringBuilder etag = new StringBuilder();
etag.append("\"");
makeETagString(nodeInfo, etag);
etag.append("\"");
return etag.toString();
}
/**
* Make an ETag value for a node using the GUID and modify date/time
*/
protected final void makeETagString(FileInfo nodeInfo, StringBuilder etag)
{
// Get the modify date/time property for the node
Object modVal = nodeInfo.getProperties().get(ContentModel.PROP_MODIFIED);
etag.append(nodeInfo.getNodeRef().getId());
if ( modVal != null)
{
etag.append("_");
etag.append(DefaultTypeConverter.INSTANCE.longValue(modVal));
}
}
/**
* @return Return the null XML attribute list
*/
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)
{
return encodeURL(s, null);
}
public final static String encodeURL(String s, String userAgent)
{
try
{
if (userAgent != null && (userAgent.startsWith(WebDAV.AGENT_MICROSOFT_DATA_ACCESS_INTERNET_PUBLISHING_PROVIDER_DAV)
|| userAgent.contains(WebDAV.AGENT_INTERNET_EXPLORER)))
{
return encodeUrlReservedSymbols(s);
}
else
{
return URLEncoder.encode(s);
}
}
catch (UnsupportedEncodingException err)
{
throw new RuntimeException(err);
}
}
public final static String decodeURL(String s)
{
return URLDecoder.decode(s);
}
/**
* 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 = "&quot;"; break; //"
case '&': enc = "&amp;"; break; //&
case '<': enc = "&lt;"; break; //<
case '>': enc = "&gt;"; break; //>
//misc
case '\u20AC': enc = "&euro;"; break;
case '\u00AB': enc = "&laquo;"; break;
case '\u00BB': enc = "&raquo;"; break;
case '\u00A0': enc = "&nbsp;"; 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();
}
}
/**
* ALF-5333: Microsoft clients use ISO-8859-1 to decode WebDAV responses
* so this method should only be used for Microsoft user agents.
*
* @param string
* @return The encoded string for Microsoft clients
* @throws UnsupportedEncodingException
*/
public final static String encodeUrlReservedSymbols(String string) throws UnsupportedEncodingException
{
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)
{
// reserved
case ';': enc = URLEncoder.encode(String.valueOf(c)); break;
case '/': enc = URLEncoder.encode(String.valueOf(c)); break;
case '?': enc = URLEncoder.encode(String.valueOf(c)); break;
case ':': enc = URLEncoder.encode(String.valueOf(c)); break;
case '@': enc = URLEncoder.encode(String.valueOf(c)); break;
case '&': enc = URLEncoder.encode(String.valueOf(c)); break;
case '=': enc = URLEncoder.encode(String.valueOf(c)); break;
case '+': enc = URLEncoder.encode(String.valueOf(c)); break;
// unsafe
case '\"': enc = URLEncoder.encode(String.valueOf(c)); break;
case '#': enc = URLEncoder.encode(String.valueOf(c)); break;
case '%': enc = URLEncoder.encode(String.valueOf(c)); break;
case '>': enc = URLEncoder.encode(String.valueOf(c)); break;
case '<': enc = URLEncoder.encode(String.valueOf(c)); break;
default: 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();
}
}
public String determineSiteId(WebDAVMethod method)
{
SiteService siteService = getServiceRegistry().getSiteService();
String siteId;
try
{
FileInfo fileInfo = getNodeForPath(method.getRootNodeRef(), method.getPath(), method.getServletPath());
SiteInfo siteInfo = siteService.getSite(fileInfo.getNodeRef());
if (siteInfo != null)
{
siteId = siteInfo.getShortName();
}
else
{
throw new RuntimeException("Node is not contained by a site: " + method.getPath());
}
}
catch (Exception error)
{
siteId = EMPTY_SITE_ID;
}
return siteId;
}
public String determineTenantDomain(WebDAVMethod method)
{
TenantService tenantService = getTenantService();
String tenantDomain = tenantService.getCurrentUserDomain();
if (tenantDomain == null)
{
return TenantService.DEFAULT_DOMAIN;
}
return tenantDomain;
}
/**
* Extract the destination path for MOVE or COPY commands from the
* supplied destination URL header.
*
* @param servletPath Path prefix of the WebDAV servlet.
* @param destURL The Destination header.
* @return The path to move/copy the file to.
*/
public String getDestinationPath(String contextPath, String servletPath, String destURL)
{
if (destURL != null && destURL.length() > 0)
{
int offset = -1;
if (destURL.startsWith(HTTP_SCHEME))
{
// Set the offset to the start of the host name
offset = HTTP_SCHEME.length();
}
else if (destURL.startsWith(HTTPS_SCHEME))
{
// Set the offset to the start of the host name
offset = HTTPS_SCHEME.length();
}
// Strip the start of the path if not a relative path
if (offset != -1)
{
offset = destURL.indexOf(WebDAV.PathSeperator, offset);
if (offset != -1)
{
// Strip the host from the beginning
String strPath = destURL.substring(offset);
// If it starts with /contextPath/servletPath/ (e.g. /alfresco/webdav/path/to/file) - then
// strip the servlet path from the start of the path.
String pathPrefix = contextPath + servletPath + WebDAV.PathSeperator;
if (strPath.startsWith(pathPrefix))
{
strPath = strPath.substring(pathPrefix.length());
}
return WebDAV.decodeURL(strPath);
}
}
}
// Unable to get the path.
return null;
}
/**
* Check that the destination path is on this server and is a valid WebDAV
* path for this server
*
* @param request The request made against the WebDAV server.
* @param urlStr String
* @exception WebDAVServerException
*/
public void checkDestinationURL(HttpServletRequest request, String urlStr) throws WebDAVServerException
{
try
{
// Parse the URL
URL url = new URL(urlStr);
// Check if the path is on this WebDAV server
boolean localPath = true;
if (url.getPort() != -1 && url.getPort() != request.getServerPort())
{
// Debug
if (logger.isDebugEnabled())
logger.debug("Destination path, different server port");
localPath = false;
}
else if (url.getHost().equalsIgnoreCase(request.getServerName()) == false
&& url.getHost().equals(request.getLocalAddr()) == false)
{
// The target host may contain a domain or be specified as a numeric IP address
String targetHost = url.getHost();
if ( IPAddress.isNumericAddress( targetHost) == false)
{
String localHost = request.getServerName();
int pos = targetHost.indexOf( ".");
if ( pos != -1)
targetHost = targetHost.substring( 0, pos);
pos = localHost.indexOf( ".");
if ( pos != -1)
localHost = localHost.substring( 0, pos);
// compare the host names
if ( targetHost.equalsIgnoreCase( localHost) == false)
localPath = false;
}
else
{
try
{
// Check if the target IP address is a local address
InetAddress targetAddr = InetAddress.getByName( targetHost);
if ( NetworkInterface.getByInetAddress( targetAddr) == null)
localPath = false;
}
catch (Exception ex)
{
// DEBUG
if ( logger.isDebugEnabled())
logger.debug("Failed to check target IP address, " + targetHost);
localPath = false;
}
}
// Debug
if (localPath == false && logger.isDebugEnabled())
{
logger.debug("Destination path, different server name/address");
logger.debug(" URL host=" + url.getHost() + ", ServerName=" + request.getServerName() + ", localAddr=" + request.getLocalAddr());
}
}
else if (!url.getPath().startsWith(getUrlPathPrefix(request)))
{
// Debug
if (logger.isDebugEnabled())
logger.debug("Destination path, different serlet path");
localPath = false;
}
// If the URL does not refer to this WebDAV server throw an
// exception
if (localPath != true)
throw new WebDAVServerException(HttpServletResponse.SC_BAD_GATEWAY);
}
catch (MalformedURLException ex)
{
// Debug
if (logger.isDebugEnabled())
logger.debug("Bad destination path, " + urlStr);
throw new WebDAVServerException(HttpServletResponse.SC_BAD_GATEWAY);
}
}
public void setUrlPathPrefix(String urlPathPrefix)
{
m_urlPathPrefix = urlPathPrefix;
}
public String getUrlPathPrefix(HttpServletRequest request)
{
StringBuilder urlStr = null;
if (StringUtils.hasText(m_urlPathPrefix))
{
// A specific prefix has been configured in, so use it.
urlStr = new StringBuilder(m_urlPathPrefix);
}
else
{
// Extract the path prefix from the request, using the servlet path as a guide.
// e.g. "/preamble/servlet-mapping/folder/file.txt"
// with a servlet path of "/servlet-mapping"
// would result in a path prefix of "/preamble/servlet-mapping" being discovered.
urlStr = new StringBuilder(request.getRequestURI());
String servletPath = request.getServletPath();
int rootPos = urlStr.indexOf(servletPath);
if (rootPos != -1)
{
urlStr.setLength(rootPos + servletPath.length());
}
}
// Ensure the prefix ends in the path separator.
if (urlStr.charAt(urlStr.length() - 1) != PathSeperatorChar)
{
urlStr.append(PathSeperator);
}
return urlStr.toString();
}
public String getRepositoryPath(HttpServletRequest request)
{
// Try and get the path
String strPath = null;
try
{
strPath = WebDAVHelper.decodeURL(request.getRequestURI());
}
catch (Exception ex) {}
// Find the servlet path and trim from the request path
String servletPath = request.getServletPath();
int rootPos = strPath.indexOf(servletPath);
if ( rootPos != -1)
{
strPath = strPath.substring( rootPos);
}
// If we failed to get the path from the request try and get the path from the servlet path
if (strPath == null)
{
strPath = request.getServletPath();
}
if (strPath == null || strPath.length() == 0)
{
// If we still have not got a path then default to the root directory
strPath = WebDAV.RootPath;
}
else if (strPath.startsWith(request.getServletPath()))
{
// Check if the path starts with the base servlet path
int len = request.getServletPath().length();
if (strPath.length() > len)
{
strPath = strPath.substring(len);
}
else
{
strPath = WebDAV.RootPath;
}
}
// Make sure there are no trailing slashes
if (strPath.length() > 1 && strPath.endsWith(WebDAV.PathSeperator))
{
strPath = strPath.substring(0, strPath.length() - 1);
}
// Return the path
return strPath;
}
/**
* Indicates if the node is unlocked or the current user has a WRITE_LOCK<p>
*
* @see LockService#isLockedOrReadOnly(NodeRef)
*
* @param nodeRef the node reference
*/
public boolean isLockedOrReadOnly(final NodeRef nodeRef)
{
return LockUtils.isLockedOrReadOnly(nodeRef, m_serviceRegistry.getLockService());
}
}