mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-14 17:58:59 +00:00
43628: Fix for ALF-16299 - On full user profile, in the 'Sites' tab, the site descriptions are not aligned when a long site description is provided. 43639: ALF-16701: use newly released Spring Surf 1.2.0-M1 rather than a SNAPSHOT 43644: ALF-16527: webscript person.lib.ftl does not include all attributes for users out of the box. - added missing organizationId attribute. 43649: ALF-16756: WebDAV: An error occurs on drag&drop content from local machine to alfresco when inbound move rule configured. 43651: ALF-16756: Fixed typos - I took this code in good faith! 43659: ALF-16006: MT: Document Library is absent after upgrade from 3.4.x to 4.1.x (eg. 3.4.10 -> 4.1.1) - More patch dependencies required 43666: ALF-16833 / MNT-187: Fixed regression in inbound rule firing caused by the fix to ALF-14744 - Inbound rules were not firing for newly created content nodes that had null content properties (e.g. dataListItems) - Now the ALF-14744 fix has been revised so that inbound rules are only not fired for new nodes with zero-length content, as possibly created by the OSX / Windows 7 WebDAV clients during an upload, and only if policy.content.update.ignoreEmpty=true (the default) - OnContentUpdateRuleTrigger removed. Now all content property updates are handled by OnPropertyUpdateRuleTrigger so that we can have this subtle treatment of 'empty' content properties. - Reverted ALF-14744 changes to RuleServiceCoverageTest and added new tests for 'empty' content and ASPECT_NO_CONTENT - Updated RuleTriggerTest in line with changes 43675: Merged V4.1 to V4.1-BUG-FIX 43674: Merged PATCHES/V4.1.1 to V4.1 43673: Merged V3.4-BUG-FIX to PATCHES/V4.1.1 43672: ALF-16834, ALF-16833, MNT-187: Fix unit test failures and 'fine tune' logic to handle multiple updates within a single transaction creating a node 43676: ALF-12324: Can't delete site with WQS content - Don't try to add nodes to the publish queue on site deletion 43689: Fix for ALF-14207 - cm:link not correctly handled in Share's doclist when users don't have permission 43690: Fix for ALF-16538 - Wrong label on change group permission message 43696: ALF-16348 (Inconsistent error message when latest manager leaves a site) 43719: ALF-16031: "CMISChangeLog does not log Created events" 43720: ALF-14137: "When calling CMIS getAllVersions method using the OpenCMIS Browser the PreparedStatement is executed multiple times" 43722: ALF-16352: Re-instated manage permissions action to document list view 43745: Fix for ALF-15351. ContentModelFormProcessor had a TODO to handle associations defined on aspects other than those aspects already on the node for which a form is being submitted. I've added code that, when an association is added to a node, will check if the association is defined on any aspect in the system and if it is, it will let the association be created. 43757: ALF-16171: If a password for admin user is specified with a space in the end during the installation Alfresco startup fails - Added validation that will not allow the specified password to contain leading to trailing white space 43760: Fixed ALF-16317 "Labels not displaying full text when creating a rule on a folder with IE8" part 2 43769: Fix for ALF-13461. Merged PATCHES/V3.4.8 to BRANCHES/DEV/V4.1-BUG-FIX: 43344: MNT-162: Merged DEV to PATCHES/V3.4.8 43254: MNT-162: CLONE - Hidden folder "Comments" is available to searching - Removed last '/' from COMMENT_QNAMEPATH constant. 43770: ALF-15616: Merged V3.4-BUG-FIX (3.4.12) to V4.1-BUG-FIX (4.1.3) 43768: Merged Dev to V3.4-BUG-FIX (3.4.12) 43743: ALF-11956: WCM accessibility The problem with absence of initial focus on the first Web form element has been resolved in 'alfresco.xforms.XForm._loadHandler()'. This method creates all controls, defined in the form. 'alfresco.xforms.FocusResolver._findControl()' has been renamed to a public-like method ('alfresco.xforms.FocusResolver.findControl()'). Also it has been modified to introduce a possibility of searching elements, using root XForms widgets container. Some other minor fixes. 43772: Fixed ALF-16497 "Selected Items drop down box is expanded to the right in IE9" 43787: Merged in Philippe's fix for ALF-16313:unmakeTranslation() called on pivot language gives exception 43794: ALF-16155: "Notification digest accumulates and is repeatedly sent if 1 or more notification email fails" 43805: ALF-16212: Fixes double encoding issue. 43835: Fixed ALF-13595: Patches third party library date.js to prevent infinite loop. 43836: ALF-16480: Merged PATCHES/V4.1.1 to V4.1-BUG-FIX 43252: MNT-166: Document lock not removed after the lock expiry date has been reached - Changed evaluator "evaluator.doclib.metadata.isLocked" to use "evaluator.doclib.indicator.nodeLocked" and "evaluator.doclib.indicator.lockOwner" evaluator. 43253: MNT-165: "Cancel Editing" does not completely remove lock from document - Added documentLibrary cancel editing action for locked documents. 43300: MNT-171: Merged V4.1-BUG-FIX to PATCHES/V4.1.1 (modified) 43096: Fix for ALF-16283 - When document is checked out, 'Edit Online' and 'Upload New Version' options should not be visible on the original document. 43311: MNT-165: "Cancel Editing" does not completely remove lock from document - Fix for page refresh problem when cancel editing on details page 43421: MNT-186: 4.1.1.7 HF: Webscipt error on doclib page, containing locked by other users files - Change evaluator.doclib.metadata.isLocked to break circular dependency 43755: MNT-202: Upload New Version not available for a document that has been edited offline - Upload New Version is now available if editable by user (respecting locks, type of checkout, etc). 43844: ALF-16696: Merged DEV to V4.1-BUG-FIX 43734: Share nodebrowser is unable to access node of a document with MS residual properties - Use localname if no prefix is registered for a namespace uri 43864: Fixed ALF-16320 "Properties side panel collapses after editing properties in document preview window with IE8" 43866: Fixed ALF-16320 "Properties side panel collapses after editing properties in document preview window with IE8" part 2 - Checking using YAHOO.util.Event.getTarget instead 43867: Fixed ALF-16276 "'"Web View' dahslet displays 2 scrollbars in IE8." 43872: Merged V4.1 to V4.1-BUG-FIX 43622: ALF-16757: Sharepoint doesn't work correct with SSO - Fix by Pavel 43633: Latest translations from Gloria (r43623) 43636: Merged PATCHES/V4.1.1 to V4.1 43301: ALF-16811 / MNT-173: SOLR tracking spending too long evaluating paths - Too much time was being spent fetching the individual nodes in each path and there could be potentially thousands - Now we traverse all the ancestor parent associations in the cache before switching shared cache reads off, then bulk load them in one shot! 43303: ALF-16812 / MNT-174: "dictionaryDAO.init() called, yet no namespace registry for domain" after node rejoins cluster - namespaceRegistryCache is secondary to the clustered dictionaryRegistryCache, so can be a non-clustered cache. Synchronization and thread locals already in use so still thread safe. - dictionaryDAO.init() now has sole responsibility of establishing the NamespaceRegistry threadlocal within its locks, so no more risk of cyclic dependencies, race conditions, partial initialization, or registries dropping out of the cache - To avoid being confused by the 'consistent read' behaviour of a transactional cache, DictionaryDAOImpl now reads / writes directly to a shared cache. Again locks and thread locals still used so still thread safe 43334: ALF-16812 / MNT-174: Fixed failing unit tests - Because DictionaryDAOImpl now reads directly from the shared cache we need to reset it on initialization to avoid problems in multi-context unit tests 43337: ALF-16811 / MNT-173: Fix test failure. Cope with IDs of deleted nodes in getCachedAncestors() 43356: ALF-16811 / MNT-173: If we are disabling shared cache reads for the transaction, convert all existing reads and updates to avoid 'consistent read' behaviour giving us a potentially out of date node already accessed - Existing read buckets are simply thrown away - Updates are converted to removes to avoid any assumptions about existing shared cache content - New entries are left alone as they haven't come from the shared cache anyway 43410: ALF-16813 / MNT-185: Web Scripts are being endlessly re-registering in clustered environment - Due to 'consistent read' behaviour of transactional cache - As accesses are regulated by RW locks we can read straight through to the shared cache instead 43565: ALF-16814 / MNT-190: Bmlab Solr Node 2 threw unhandled NullPointerException (and possibly made solr unresponsive) - Added missing return statement 43646: ALF-15755: Rationalization of WebDAVMethod.checkNode() 43681: Merged HEAD to V4.1 43656: Fix for ALF-16683 CMIS: cannot navigate to original document was created through CMIS with CHECKED OUT version state. 43698: Latest Russian translations from Gloria 43838: ALF-16875: Merged V4.1-BUG-FIX to V4.1 43836: ALF-16480: Merged PATCHES/V4.1.1 to V4.1-BUG-FIX 43252: MNT-166: Document lock not removed after the lock expiry date has been reached - Changed evaluator "evaluator.doclib.metadata.isLocked" to use "evaluator.doclib.indicator.nodeLocked" and "evaluator.doclib.indicator.lockOwner" evaluator. 43253: MNT-165: "Cancel Editing" does not completely remove lock from document - Added documentLibrary cancel editing action for locked documents. 43300: MNT-171: Merged V4.1-BUG-FIX to PATCHES/V4.1.1 (modified) 43096: Fix for ALF-16283 - When document is checked out, 'Edit Online' and 'Upload New Version' options should not be visible on the original document. 43311: MNT-165: "Cancel Editing" does not completely remove lock from document - Fix for page refresh problem when cancel editing on details page 43421: MNT-186: 4.1.1.7 HF: Webscipt error on doclib page, containing locked by other users files - Change evaluator.doclib.metadata.isLocked to break circular dependency 43755: ALF-16890 / MNT-202: Upload New Version not available for a document that has been edited offline - Upload New Version is now available if editable by user (respecting locks, type of checkout, etc). 43868: Reverse merging r43838 - Merge in wrong direction introducing duplicate fragment into share-documentlibrary-config.xml 43871: ALF-16890: Merged PATCHES/V4.1.1 to V4.1 43755: MNT-202: Upload New Version not available for a document that has been edited offline - Upload New Version is now available if editable by user (respecting locks, type of checkout, etc). 43873: Merged V4.1 to V4.1-BUG-FIX (RECORD ONLY) 43602: ALF-16254: Merged V4.1-BUG-FIX to V4.1 43598: Merged HEAD to BRANCHES/DEV/V4.1-BUG-FIX 41906: ALF-11378: REST API has been modified to return extra information about a user whether s/he belongs to a group or not. 43612: ALF-16598: Merged V4.1-BUG-FIX to V4.1 43252: MNT-166: Document lock not removed after the lock expiry date has been reached - Changed evaluator "evaluator.doclib.metadata.isLocked" to use "evaluator.doclib.indicator.nodeLocked" and "evaluator.doclib.indicator.lockOwner" evaluator. 43254: MNT-165: "Cancel Editing" does not completely remove lock from document - Added documentLibrary cancel editing action for locked documents. 43300: MNT-171: Merged V4.1-BUG-FIX to PATCHES/V4.1.1 (modified) 43096: Fix for ALF-16283 - When document is checked out, 'Edit Online' and 'Upload New Version' options should not be visible on the original document. 43311: MNT-165: "Cancel Editing" does not completely remove lock from document - Fix for page refresh problem when cancel editing on details page 43421: MNT-186: 4.1.1.7 HF: Webscipt error on doclib page, containing locked by other users files - Change evaluator.doclib.metadata.isLocked to break circular dependency 43615: ALF-16794: Merged V4.1-BUG-FIX to V4.1 43478: MNT-181: Now WebDAV will ALWAYS preserve the original metadata and versions of ANY node that is temporarily 'moved out' in ANY kind of 'shuffle' operation - To make the source node temporarily invisible to WebDAV the client specific HIDDEN aspect features are used - WebDAVHelper.isRenameShuffle() method introduced, to parallel ALF-3856 CIFS fix and using similar system.webdav.renameShufflePattern global property to detect the start of a shuffle - WebDAVHelper converted to use proper dependency injection - CopyMethod has become a simple subclass of MoveMethod as all the hidden aspect munging is done by it - DeleteMethod now preserves hidden nodes - PropFindMethod now ignores hidden nodes - Listing methods will hide hidden nodes from WebDAV 43483: MNT-181: Corrected typo 43523: MNT-181: Corrections - WebDAVLockService.unlock() made 'harmless' to call on already-unlocked nodes - Delete method hides rather than deletes versioned nodes and working copes in case it is called by OSX Finder during a 'replace' operation 43524: MNT-181: Correction - PutMethod now 'unhides' hidden nodes and behaves as though it created them 43570: MNT-181: More corrections researched by Valery - Don't treat all moves to temporary locations as copies - just those from non-temporary locations. Avoids initial upload leaving lots of hidden files around. - Only copy the content, not the whole node including aspects to avoid versioning temporary files! - Don't version on changes to sys:clientVisibilityMask - avoids 'double versioning' - Recognize Mac .TemporaryItems folder and ._ files as temporary 43586: MNT-181: Final correction researched by Valery - Corrected system.webdav.renameShufflePattern so that it matches .TemporaryItems folder and ._ files as a full match 43616: ALF-15755: Merged V4.1-BUG-FIX to V4.1 43591: ALF-16772: If the WebDAV path of a document exceeds 255 characters, documents opened in MSOffice cannot be saved back - Interpret null nodeLockToken as not locked. 43629: Merged V4.1-BUG-FIX to V4.1 (4.1.2) 43498: Fix for ALF-16648 - Alfresco Enterprise artifacts in artifacts.alfresco.com do not provide POM files / dependencies declarations: Merged HEAD to V4.1-BUG-FIX (4.1.2) 43380: -- added site content for alfresco-platform-distribution POM 43379: -- added site documentation for alfresco-platform-distribution POM 43378: -- added site documentation for alfresco-platform-distribution POM -- deployed site for 4.2.b Community at https://artifacts.alfresco.com/nexus/content/repositories/alfresco-docs/alfresco-platform-distribution/latest/index.html -- created repository for Enterprise docs and added url in the appropriate edition properties 43273: Use property to define POI version 42966: ALF-14353 - Added platform distribution POM to standard maven-deploy procedure 42965: ALF-14353 - added alfresco-platform-distribution to provide a Maven release descriptor (dependencyManagement) per each Community / Enterprise release -- moved maven-ant-tasks not to be in the runtime lib -- added platform distribution pom in the SDK folder -- updated maven.xml to deploy filter and deploy the appropriate platform-distribution POM per each releae -- in maven.xml moved configure-release and configure-snapshot goals to maven-env-prerequisites -- updated sdk readme to explain the presence of alfresco-platform-distribution POM 42912: -- updated README header on the POM specifying it's NOT usable to build Alfresco -- make a clear reference to the POMs that get deployed by pom-experimental.xml being usable for development 42842: ALF-14353: Fix artifactId alfresco-jlan -> alfresco-jlan-embed 41883: ALF-14353 - fixed multiple Maven build issues. Now mvn clean install -f pom-experimental.xml works fine. Also deployed Spring Surf 1.2.0-SNAPSHOT so proper Surf version is retrieved 41882: added pre-requisites to build POMs successfully with mvn clean install -f pom-experimental.xml 43634: Merged V4.1-BUG-FIX to V4.1 43386: ALF-13091: Prevent bean post processor propagation to child application contexts. Remove Jsr250BeanPostPorcessor from the CXF configuration, to prevent strange interaction with component scanning. 43641: Merged V4.1-BUG-FIX to V4.1 (4.1.2) 43639: ALF-16701: use newly released Spring Surf 1.2.0-M1 in POM files rather than a SNAPSHOT 43645: Merged V4.1-BUG-FIX to V4.1 43644: ALF-16527: webscript person.lib.ftl does not include all attributes for users out of the box. - added missing organizationId attribute. 43660: Merged V4.1-BUG-FIX to V4.1 43659: ALF-16006: MT: Document Library is absent after upgrade from 3.4.x to 4.1.x (eg. 3.4.10 -> 4.1.1) - More patch dependencies required 43669: Merged V4.1-BUG-FIX to V4.1 43666: ALF-16833 / MNT-187: Fixed regression in inbound rule firing caused by the fix to ALF-14744 - Inbound rules were not firing for newly created content nodes that had null content properties (e.g. dataListItems) - Now the ALF-14744 fix has been revised so that inbound rules are only not fired for new nodes with zero-length content, as possibly created by the OSX / Windows 7 WebDAV clients during an upload, and only if policy.content.update.ignoreEmpty=true (the default) - OnContentUpdateRuleTrigger removed. Now all content property updates are handled by OnPropertyUpdateRuleTrigger so that we can have this subtle treatment of 'empty' content properties. - Reverted ALF-14744 changes to RuleServiceCoverageTest and added new tests for 'empty' content and ASPECT_NO_CONTENT - Updated RuleTriggerTest in line with changes 43697: Merged V4.1-BUG-FIX to V4.1 43689: Fix for ALF-14207 - cm:link not correctly handled in Share's doclist when users don't have permission 43761: Merged V4.1-BUG-FIX to V4.1 43760: Fixed ALF-16317 "Labels not displaying full text when creating a rule on a folder with IE8" part 2 43796: Merged V4.1-BUG-FIX to V4.1 43795: Fix for ALF-16254 - "Leave Site" behaviour for group based site membership: Blah, Blah, Blah! 43883: Fix for ALF-12711. Separated preparation of email message from sending of email message as described in analysis in JIRA. Did not merge the fix provided as MailActionExecuter.java.diff but reimplemented a fix based on that. 43888: ALF-16781: Merged V3.4-BUG-FIX (3.4.12) to V4.1-BUG-FIX (4.1.3) 43887: ALF-16898 CLONE - Transformation Server history shows incorrect transformation "To" type for images - ImageTransformActionExecuter ("Transform and Copy Image") re-factored to use ContentService rather than hard coded to ImageMagick 43900: Merged V4.1 to V4.1-BUG-FIX 43898: Merged PATCHES/V4.1.1 to V4.1 43708: ALF-16903 / MNT-203: ACL changes not propagated between two non clustered Alfresco instances hitting same Solr and Database - Although SOLRTrackingComponentImpl was not trusting the cache when tracking node transactions, it was still trusting the cache for ACL change sets - This mean that when the SOLR node is using an Alfresco out of the cluster (which we are supposed to support) it could see stale ACLs - Fixed this by using the same strategy we use in AbstractNodeDAO - Now the main acl CRUD cache is set to ignore the shared cache during SOLR tracking - All secondary ACL caches are keyed by ACL ID AND version, so it prevents the possibility of retrieving a stale cached ACL for an old version 43713: ALF-16903 / MNT-203: ACL changes not propagated between two non clustered Alfresco instances hitting same Solr and Database - Fix test failures - deleteAccessControlEntries must 'touch' all the ACLs it affects to keep caches valid - createAccessControlList should return the newly-versioned ACL 43736: ALF-16904 / MNT-204: Index showing no progress on Solr server - SAP's tracking thread got stuck indefinitely waiting on a content response after sending a request, probably due to some misbehaving proxy or balancer - Now we make it possible to recover from this - A new alfresco.socketTimeout parameter is now supported in solrcore.properties - It specifies the number of milliseconds SOLR will wait before giving up waiting for data on an HTTP connection - The default is still zero which means wait indefinitely 43759: ALF-16904 / MNT-204: Fixed compilation error 43899: Merged PATCHES/V4.1.1 to V4.1 (RECORD ONLY) 43667: Merged V4.1-BUG-FIX to PATCHES/V4.1.1 43666: ALF-16833 / MNT-187: Fixed regression in inbound rule firing caused by the fix to ALF-14744 - Inbound rules were not firing for newly created content nodes that had null content properties (e.g. dataListItems) - Now the ALF-14744 fix has been revised so that inbound rules are only not fired for new nodes with zero-length content, as possibly created by the OSX / Windows 7 WebDAV clients during an upload, and only if policy.content.update.ignoreEmpty=true (the default) - OnContentUpdateRuleTrigger removed. Now all content property updates are handled by OnPropertyUpdateRuleTrigger so that we can have this subtle treatment of 'empty' content properties. - Reverted ALF-14744 changes to RuleServiceCoverageTest and added new tests for 'empty' content and ASPECT_NO_CONTENT - Updated RuleTriggerTest in line with changes 43901: Merged V3.4-BUG-FIX to V4.1-BUG-FIX 43571: Merged DEV to V3.4-BUG-FIX 43569: ALF-16222: It's impossible to delete a file/message via IMAP using Microsoft Entourage 2008 in MacOSX 10.8 fixed: UID failed.Existing file or folder error on attempt to delete file if deleted items already contains file with the same name. modified: AttachmentExtractor modified to avoid code dublication. 43599: Fix for ALF-16505 - Discussion topics are sorted in ascending order (oldest first and newest last) when selecting 'All topics' 43610: Merged Dev to V3.4-BUGFIX (3.4.12) 43416: ALF-16470: SPP:Meeting recurent events are displayed incorrect in share calendar in all tabs (Day, Week, Month) Incorrect lucene query to search for events (it was search for events ONLY after fromDate, that gets from browser query. And recursive events, that have startDate before browser's query date, wasn't included into summary query for search for events. Start date is incorrect and is sets two times. Interval for month of end date is to small. Recursive event, that was started in previous month, and ends in current month, isn't included into result of search. For events, that ends on next date after start date, displays (view by month) only start date. 43625: ALF-11817: Cope with incomplete lock token headers from Microsoft-WebDAV-MiniRedir without the enclosing <> by just consuming the whole string 43670: ALF-11817: Prevent auto-hidden dot underscore files from reappearing on a put by checking for a shuffle path before 'unhiding' 43746: Merged DEV to V3.4-BUG-FIX (with corrections) 43692: ALF-16808 Webdav: Two versions of document have been added after the document has been rewritten once more via drag and drop action. 1. Checked whether the current content property is empty. 2. Disabled the versionable aspect. 3. Added the new content to the node. 43763: Fix for ALF-14828 - Incorrect behavior on delete action (WCMQS site) 43771: Fix for ALF-12752 - Custom form appearance parameters not accounted for causing ovverlapped textareas xforms changing their height 43773: Merged DEV to V3.4-BUG-FIX (3.4.12) 42010: ALF-14040: Event start/end time displays incorrect on MS Outlook Calendar and Calendar of created Meeting workspace 1) Send a date for "Site Calendar" dashlet in ISO-8601 format (like in v4.1.1), then after transformation to client's time zone it is displayed correct 2) For "My Calendar" dashlet we need to take into account that if the event is "all day event", then date should be used without time zone transformation 43804: ALF-12326 HomeFolderProviderSynchronizer fails to move any user space which has a rule configured on it 43837: Fixes ALF-12145: Date.js patches merged to 3.4 Merged BRANCHES/DEV/V4.0-BUG-FIX to BRANCHES/DEV/V3.4-BUG-FIX: 36202: ALF-13483: Japanese: Incorrect date handle in a date Input filed Merged BRANCHES/DEV/V4.1-BUG-FIX to BRANCHES/DEV/V3.4-BUG-FIX: 43835: Fixed ALF-13595: Patches third party library date.js to prevent infinite loop. 43839: ALF-16869: Merged PATCHES/V3.4.8 to V3.4-BUG-FIX 43344: MNT-162: Merged DEV to PATCHES/V3.4.8 43254: MNT-162: CLONE - Hidden folder "Comments" is available to searching - Removed last '/' from COMMENT_QNAMEPATH constant. 43902: Merged V3.4-BUG-FIX to V4.1-BUG-FIX (RECORD ONLY) 43177: Merged DEV to V3.4-BUG-FIX 43087: ALF-16474: Records Management groups were not deleted after removing RM site - Delete Records Management groups on ASPECT_RECORDS_MANAGEMENT_ROOT delete - Backport of RM-190 from RM 2.0 43228: ALF-16266: Merged HEAD to V3.4-BUG-FIX 32846: Fixes: ALF-10519: Issues with translation of roles. 31413: Fixes: ALF-10519 - Internationalises the role names for the repo browser's manage permissions page & makes these available to other pages through common.properties 43229: ALF-16266: Fix conflict data left in the properties file. 43624: ALF-11817: Merged PATCHES/V4.0.2 to V3.4-BUG-FIX 43587: Merged BRANCHES/V4.1-BUG-FIX to PATCHES/V4.0.2 42363: ALF-16213: renaming versioned file results in file being deleted. 43478: MNT-181: Now WebDAV will ALWAYS preserve the original metadata and versions of ANY node that is temporarily 'moved out' in ANY kind of 'shuffle' operation - To make the source node temporarily invisible to WebDAV the client specific HIDDEN aspect features are used - WebDAVHelper.isRenameShuffle() method introduced, to parallel ALF-3856 CIFS fix and using similar system.webdav.renameShufflePattern global property to detect the start of a shuffle - WebDAVHelper converted to use proper dependency injection - CopyMethod has become a simple subclass of MoveMethod as all the hidden aspect munging is done by it - DeleteMethod now preserves hidden nodes - PropFindMethod now ignores hidden nodes - Listing methods will hide hidden nodes from WebDAV 43483: MNT-181: Corrected typo 43523: MNT-181: Corrections - WebDAVLockService.unlock() made 'harmless' to call on already-unlocked nodes - Delete method hides rather than deletes versioned nodes and working copies in case it is called by OSX Finder during a 'replace' operation 43524: MNT-181: Correction - PutMethod now 'unhides' hidden nodes and behaves as though it created them 43570: MNT-181: More corrections researched by Valery - Don't treat all moves to temporary locations as copies - just those from non-temporary locations. Avoids initial upload leaving lots of hidden files around. - Only copy the content, not the whole node including aspects to avoid versioning temporary files! - Don't version on changes to sys:clientVisibilityMask - avoids 'double versioning' - Recognize Mac .TemporaryItems folder and ._ files as temporary 43586: MNT-181: Final correction researched by Valery - Corrected system.webdav.renameShufflePattern so that it matches .TemporaryItems folder and ._ files as a full match 43671: ALF-16834: Merged V4.1-BUG-FIX to V3.4-BUG-FIX 43666: ALF-16833 / MNT-187: Fixed regression in inbound rule firing caused by the fix to ALF-14744 - Inbound rules were not firing for newly created content nodes that had null content properties (e.g. dataListItems) - Now the ALF-14744 fix has been revised so that inbound rules are only not fired for new nodes with zero-length content, as possibly created by the OSX / Windows 7 WebDAV clients during an upload, and only if policy.content.update.ignoreEmpty=true (the default) - OnContentUpdateRuleTrigger removed. Now all content property updates are handled by OnPropertyUpdateRuleTrigger so that we can have this subtle treatment of 'empty' content properties. - Reverted ALF-14744 changes to RuleServiceCoverageTest and added new tests for 'empty' content and ASPECT_NO_CONTENT - Updated RuleTriggerTest in line with changes 43842: Merged V4.0-BUG-FIX to V3.4-BUG-FIX 33387: ALF-12492 - Email with empty subject sent to Alfresco by SMTP cause Null pointer Exception 43843: ALF-16717: Merged V4.1-BUG-FIX to V3.4-BUG-FIX 43314: ALF-16575 - Email server does not accept email where Subject ends with a period git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@43914 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
1692 lines
59 KiB
Java
1692 lines
59 KiB
Java
/*
|
|
* Copyright (C) 2005-2012 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.BufferedReader;
|
|
import java.io.CharArrayWriter;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStreamReader;
|
|
import java.io.OutputStream;
|
|
import java.net.SocketException;
|
|
import java.util.Date;
|
|
import java.util.HashMap;
|
|
import java.util.Iterator;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.LinkedList;
|
|
import java.util.Map;
|
|
import java.util.Map.Entry;
|
|
import java.util.Set;
|
|
import java.util.regex.Pattern;
|
|
|
|
import javax.servlet.ServletInputStream;
|
|
import javax.servlet.http.HttpServletRequest;
|
|
import javax.servlet.http.HttpServletRequestWrapper;
|
|
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.model.filefolder.HiddenAspect;
|
|
import org.alfresco.repo.model.filefolder.HiddenAspect.Visibility;
|
|
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
|
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
|
|
import org.alfresco.repo.security.permissions.AccessDeniedException;
|
|
import org.alfresco.repo.tenant.TenantService;
|
|
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
|
import org.alfresco.service.ServiceRegistry;
|
|
import org.alfresco.service.cmr.action.ActionService;
|
|
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.ChildAssociationRef;
|
|
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.search.SearchService;
|
|
import org.alfresco.service.cmr.security.AuthenticationService;
|
|
import org.alfresco.service.cmr.security.PermissionService;
|
|
import org.alfresco.service.namespace.NamespaceService;
|
|
import org.alfresco.service.transaction.TransactionService;
|
|
import org.alfresco.util.FileFilterMode;
|
|
import org.alfresco.util.FileFilterMode.Client;
|
|
import org.alfresco.util.TempFileProvider;
|
|
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.springframework.util.FileCopyUtils;
|
|
import org.w3c.dom.Document;
|
|
import org.xml.sax.Attributes;
|
|
import org.xml.sax.InputSource;
|
|
import org.xml.sax.SAXException;
|
|
import org.xml.sax.SAXParseException;
|
|
|
|
|
|
/**
|
|
* Abstract base class for all the WebDAV method handling classes
|
|
*
|
|
* @author gavinc
|
|
*/
|
|
public abstract class WebDAVMethod
|
|
{
|
|
// Log output
|
|
|
|
private static final String VERSION_NUM_PATTERN = "\\d+\\.\\d+(\\.\\d+)?";
|
|
|
|
protected static Log logger = LogFactory.getLog("org.alfresco.webdav.protocol");
|
|
|
|
// Output formatted XML in the response
|
|
|
|
private static final boolean XMLPrettyPrint = true;
|
|
|
|
// Mapping of User-Agent pattern to response status code
|
|
// used to determine which status code should be returned for AccessDeniedException
|
|
|
|
private static final Map<String, Integer> accessDeniedStatusCodes = new LinkedHashMap<String, Integer>();
|
|
static
|
|
{
|
|
accessDeniedStatusCodes.put("^WebDAVLib/" + VERSION_NUM_PATTERN + "$",
|
|
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
|
accessDeniedStatusCodes.put("^WebDAVFS/" + VERSION_NUM_PATTERN + " \\(\\d+\\)\\s+Darwin/" +
|
|
VERSION_NUM_PATTERN + "\\s+\\(.*\\)$",
|
|
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
|
accessDeniedStatusCodes.put(".*", HttpServletResponse.SC_FORBIDDEN);
|
|
}
|
|
|
|
// Servlet request/response
|
|
|
|
protected HttpServletRequest m_request;
|
|
protected HttpServletResponse m_response;
|
|
private File m_requestBody;
|
|
private ServletInputStream m_inputStream;
|
|
private CharArrayWriter m_xmlWriter;
|
|
private BufferedReader m_reader;
|
|
|
|
// WebDAV helper
|
|
|
|
protected WebDAVHelper m_davHelper;
|
|
|
|
// Root node
|
|
|
|
protected NodeRef m_rootNodeRef;
|
|
|
|
// Repository path
|
|
|
|
protected String m_strPath = null;
|
|
|
|
// User Agent
|
|
|
|
protected String m_userAgent = null;
|
|
|
|
// If header conditions
|
|
|
|
protected LinkedList<Condition> m_conditions = null;
|
|
|
|
// If header resource-tag
|
|
|
|
protected String m_resourceTag = null;
|
|
|
|
// Depth header
|
|
|
|
protected int m_depth = WebDAV.DEPTH_INFINITY;
|
|
|
|
// request scope
|
|
protected Map<NodeRef, NodeRef> m_childToParent = new HashMap<NodeRef, NodeRef>();
|
|
protected Map<NodeRef, LockInfo> m_parentLockInfo = new HashMap<NodeRef, LockInfo>();
|
|
|
|
private String siteId;
|
|
|
|
private String tenantDomain;
|
|
|
|
/**
|
|
* Default constructor
|
|
*/
|
|
public WebDAVMethod()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Set the request/response details
|
|
*
|
|
* @param req
|
|
* HttpServletRequest
|
|
* @param resp
|
|
* HttpServletResponse
|
|
* @param registry
|
|
* ServiceRegistry
|
|
* @param rootNode
|
|
* NodeRef
|
|
*/
|
|
public void setDetails(final HttpServletRequest req, HttpServletResponse resp, WebDAVHelper davHelper,
|
|
NodeRef rootNode)
|
|
{
|
|
// Wrap the request so that it is 'retryable'. Calls to getInputStream() and getReader() will result in the
|
|
// request body being read into an intermediate file.
|
|
this.m_request = new HttpServletRequestWrapper(req)
|
|
{
|
|
|
|
@Override
|
|
public ServletInputStream getInputStream() throws IOException
|
|
{
|
|
if (WebDAVMethod.this.m_reader != null)
|
|
{
|
|
throw new IllegalStateException("Reader in use");
|
|
}
|
|
if (WebDAVMethod.this.m_inputStream == null)
|
|
{
|
|
final FileInputStream in = new FileInputStream(getRequestBodyAsFile(req));
|
|
WebDAVMethod.this.m_inputStream = new ServletInputStream()
|
|
{
|
|
|
|
@Override
|
|
public int read() throws IOException
|
|
{
|
|
return in.read();
|
|
}
|
|
|
|
@Override
|
|
public int read(byte b[]) throws IOException
|
|
{
|
|
return in.read(b);
|
|
}
|
|
|
|
@Override
|
|
public int read(byte b[], int off, int len) throws IOException
|
|
{
|
|
return in.read(b, off, len);
|
|
}
|
|
|
|
@Override
|
|
public long skip(long n) throws IOException
|
|
{
|
|
return in.skip(n);
|
|
}
|
|
|
|
@Override
|
|
public int available() throws IOException
|
|
{
|
|
return in.available();
|
|
}
|
|
|
|
@Override
|
|
public void close() throws IOException
|
|
{
|
|
in.close();
|
|
}
|
|
|
|
@Override
|
|
public void mark(int readlimit)
|
|
{
|
|
in.mark(readlimit);
|
|
}
|
|
|
|
@Override
|
|
public void reset() throws IOException
|
|
{
|
|
in.reset();
|
|
}
|
|
|
|
@Override
|
|
public boolean markSupported()
|
|
{
|
|
return in.markSupported();
|
|
}
|
|
};
|
|
}
|
|
|
|
return WebDAVMethod.this.m_inputStream;
|
|
}
|
|
|
|
@Override
|
|
public BufferedReader getReader() throws IOException
|
|
{
|
|
if (WebDAVMethod.this.m_inputStream != null)
|
|
{
|
|
throw new IllegalStateException("Input Stream in use");
|
|
}
|
|
if (WebDAVMethod.this.m_reader == null)
|
|
{
|
|
String encoding = req.getCharacterEncoding();
|
|
WebDAVMethod.this.m_reader = new BufferedReader(new InputStreamReader(new FileInputStream(
|
|
getRequestBodyAsFile(req)), encoding == null ? "ISO-8859-1" : encoding));
|
|
}
|
|
|
|
return WebDAVMethod.this.m_reader;
|
|
}
|
|
|
|
};
|
|
this.m_response = resp;
|
|
this.m_davHelper = davHelper;
|
|
this.m_rootNodeRef = rootNode;
|
|
|
|
this.m_strPath = m_davHelper.getRepositoryPath(m_request);
|
|
}
|
|
|
|
private File getRequestBodyAsFile(HttpServletRequest req) throws IOException
|
|
{
|
|
if (this.m_requestBody == null)
|
|
{
|
|
this.m_requestBody = TempFileProvider.createTempFile("webdav_" + req.getMethod() + "_", ".bin");
|
|
OutputStream out = new FileOutputStream(this.m_requestBody);
|
|
int bytesRead = FileCopyUtils.copy(req.getInputStream(), out);
|
|
|
|
// ALF-7377: check for corrupt request
|
|
int contentLength = req.getIntHeader(WebDAV.HEADER_CONTENT_LENGTH);
|
|
if (contentLength >= 0 && contentLength != bytesRead)
|
|
{
|
|
throw new IOException("Request body does not have specified Content Length");
|
|
}
|
|
}
|
|
return this.m_requestBody;
|
|
}
|
|
|
|
/**
|
|
* Override and return <tt>true</tt> if the method is a query method only. The default implementation
|
|
* returns <tt>false</tt>.
|
|
*
|
|
* @return Returns <tt>true</tt> if the method transaction may be read-only
|
|
*/
|
|
protected boolean isReadOnly()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Return the property find depth
|
|
*
|
|
* @return int
|
|
*/
|
|
public final int getDepth()
|
|
{
|
|
return m_depth;
|
|
}
|
|
|
|
/**
|
|
* Executes the method, wrapping the call to {@link #executeImpl()} in an appropriate transaction
|
|
* and handling the error conditions.
|
|
* @throws IOException
|
|
*/
|
|
public void execute() throws WebDAVServerException
|
|
{
|
|
// Parse the HTTP headers
|
|
parseRequestHeaders();
|
|
|
|
// Parse the HTTP body
|
|
try
|
|
{
|
|
parseRequestBody();
|
|
}
|
|
catch (WebDAVServerException e)
|
|
{
|
|
if (e.getCause() != null && e.getCause() instanceof SAXParseException)
|
|
{
|
|
SAXParseException saxParseEx = (SAXParseException) e.getCause();
|
|
if (logger.isTraceEnabled())
|
|
{
|
|
// Include stack trace.
|
|
logger.trace("Malformed request body", saxParseEx);
|
|
}
|
|
else if (logger.isDebugEnabled())
|
|
{
|
|
// Log message only.
|
|
logger.debug("Malformed request body: " + saxParseEx.getMessage());
|
|
}
|
|
|
|
try
|
|
{
|
|
m_response.sendError(e.getHttpStatusCode());
|
|
}
|
|
catch (IOException ioe)
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Unable to send status code", ioe);
|
|
}
|
|
}
|
|
// Halt processing.
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Rethrow the exception, as we haven't dealt with it here.
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
m_userAgent = m_request.getHeader(WebDAV.HEADER_USER_AGENT);
|
|
|
|
RetryingTransactionCallback<Object> executeImplCallback = new RetryingTransactionCallback<Object>()
|
|
{
|
|
public Object execute() throws Exception
|
|
{
|
|
// Reset the request input stream / reader state
|
|
WebDAVMethod.this.m_inputStream = null;
|
|
WebDAVMethod.this.m_reader = null;
|
|
|
|
// cache current session
|
|
getDAVHelper().getLockService().setCurrentSession(m_request.getSession());
|
|
|
|
executeImpl();
|
|
return null;
|
|
}
|
|
};
|
|
try
|
|
{
|
|
boolean isReadOnly = isReadOnly();
|
|
// Execute the method
|
|
getTransactionService().getRetryingTransactionHelper().doInTransaction(executeImplCallback, isReadOnly);
|
|
generateResponseImpl();
|
|
}
|
|
catch (AccessDeniedException e)
|
|
{
|
|
// Return a forbidden status
|
|
throw new WebDAVServerException(getStatusForAccessDeniedException(), e);
|
|
}
|
|
catch (Throwable e)
|
|
{
|
|
if (e instanceof WebDAVServerException)
|
|
{
|
|
throw (WebDAVServerException) e;
|
|
}
|
|
else if (e.getCause() instanceof WebDAVServerException)
|
|
{
|
|
throw (WebDAVServerException) e.getCause();
|
|
}
|
|
else
|
|
{
|
|
boolean logOnly = false;
|
|
|
|
Throwable t = e;
|
|
while ((t = t.getCause()) != null)
|
|
{
|
|
if (t instanceof SocketException)
|
|
{
|
|
logOnly = true;
|
|
|
|
// The client aborted the connection - we can't do much about this, except log it.
|
|
if (logger.isTraceEnabled() || logger.isDebugEnabled())
|
|
{
|
|
String message = "Client dropped connection [uri=" + m_request.getRequestURI() + "]";
|
|
|
|
if (logger.isTraceEnabled())
|
|
{
|
|
// Include a stack trace when trace is enabled.
|
|
logger.trace(message, e);
|
|
}
|
|
else if (logger.isDebugEnabled())
|
|
{
|
|
// Just a message for debug-level output.
|
|
logger.debug(message);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Convert error to a server error
|
|
if (!logOnly)
|
|
{
|
|
throw new WebDAVServerException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
cleanUp();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clean up resources if about to finish processing the request.
|
|
*/
|
|
private void cleanUp()
|
|
{
|
|
// Remove temporary file if created
|
|
if (this.m_requestBody != null)
|
|
{
|
|
try
|
|
{
|
|
this.m_requestBody.delete();
|
|
this.m_requestBody = null;
|
|
}
|
|
catch (Throwable t)
|
|
{
|
|
WebDAVMethod.logger.error("Failed to delete temp file", t);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
/**
|
|
* Does nothing unless overridden - for reasons of backwards compatibility. Subclasses
|
|
* implementing this method should separate the WebDAV method execution logic from
|
|
* response generation logic. Execution logic should be contained in the {@link #executeImpl} method
|
|
* and should NOT contain any code that writes to the response. Conversely response generation logic
|
|
* should NOT contain any code relating to the desired effect of the WebDAV method (e.g. setting properties
|
|
* on a node) and should be contained purely within this method.
|
|
* <p>
|
|
* Older methods, until refactored will not override this method, relying only on {@link #executeImpl()}.
|
|
*/
|
|
protected void generateResponseImpl() throws 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();
|
|
if (m_request.getCharacterEncoding() == null)
|
|
{
|
|
// Let the XML parser work out the encoding if it is not explicitly declared in the HTTP header
|
|
body = builder.parse(new InputSource(m_request.getInputStream()));
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Parses "Depth" request header
|
|
*
|
|
* @throws WebDAVServerException
|
|
*/
|
|
protected void parseDepthHeader() throws WebDAVServerException
|
|
{
|
|
// Store the Depth header as this is used by several WebDAV methods
|
|
|
|
String strDepth = m_request.getHeader(WebDAV.HEADER_DEPTH);
|
|
if (strDepth != null && strDepth.length() > 0)
|
|
{
|
|
if (strDepth.equals(WebDAV.ZERO))
|
|
{
|
|
m_depth = WebDAV.DEPTH_0;
|
|
}
|
|
else if (strDepth.equals(WebDAV.ONE))
|
|
{
|
|
m_depth = WebDAV.DEPTH_1;
|
|
}
|
|
else
|
|
{
|
|
m_depth = WebDAV.DEPTH_INFINITY;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parses "If" header of the request.
|
|
* Stores conditions that should be checked.
|
|
* Parses both No-tag-list and Tagged-list formats
|
|
* See "10.4.2 Syntax" paragraph of the WebDAV specification for "If" header format.
|
|
*
|
|
*/
|
|
protected void 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("<"))
|
|
{
|
|
m_resourceTag = strIf.substring(1, strIf.indexOf(">"));
|
|
strIf = strIf.substring(m_resourceTag.length() + 3);
|
|
}
|
|
|
|
m_conditions = new LinkedList<Condition>();
|
|
String[] parts = strIf.split("\\) \\(");
|
|
for (int i = 0; i < parts.length; i++)
|
|
{
|
|
|
|
String partString = parts[i].replaceAll("\\(", "").replaceAll("\\)", "");
|
|
|
|
Condition c = new Condition();
|
|
String[] conditions = partString.split(" ");
|
|
|
|
for (int j = 0; j < conditions.length; j++)
|
|
{
|
|
boolean fNot = false;
|
|
String eTag = null;
|
|
String lockToken = null;
|
|
|
|
if (WebDAV.HEADER_KEY_NOT.equals(conditions[j]))
|
|
{
|
|
// Check if Not keyword followed by State-token or entity-tag
|
|
if (j == (conditions.length - 1))
|
|
{
|
|
throw new WebDAVServerException(HttpServletResponse.SC_PRECONDITION_FAILED);
|
|
}
|
|
fNot = true;
|
|
j++;
|
|
}
|
|
|
|
// read State-token
|
|
int index = conditions[j].indexOf('<');
|
|
if (index != -1)
|
|
{
|
|
try
|
|
{
|
|
String s = conditions[j].substring(index + 1, conditions[j].indexOf(">"));
|
|
if (!s.startsWith(WebDAV.OPAQUE_LOCK_TOKEN))
|
|
{
|
|
if(!fNot)
|
|
{
|
|
throw new WebDAVServerException(HttpServletResponse.SC_PRECONDITION_FAILED);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lockToken = s;
|
|
c.addLockTocken(lockToken, fNot);
|
|
}
|
|
}
|
|
catch (IndexOutOfBoundsException e)
|
|
{
|
|
throw new WebDAVServerException(HttpServletResponse.SC_PRECONDITION_FAILED);
|
|
}
|
|
}
|
|
|
|
// read entity-tag
|
|
index = conditions[j].indexOf("[\"");
|
|
if (index != -1)
|
|
{
|
|
// TODO: implement parsing of weak ETags: W/"123..".
|
|
eTag = conditions[j].substring(index + 1, conditions[j].indexOf("]"));
|
|
c.addETag(eTag, fNot);
|
|
}
|
|
|
|
}
|
|
m_conditions.add(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
|
|
/**
|
|
* Convenience method to return the search service
|
|
*
|
|
* @return SearchService
|
|
*/
|
|
protected final SearchService getSearchService()
|
|
{
|
|
return m_davHelper.getSearchService();
|
|
}
|
|
|
|
/**
|
|
* Convenience method to return the namespace service
|
|
*
|
|
* @return NamespaceService
|
|
*/
|
|
protected final NamespaceService getNamespaceService()
|
|
{
|
|
return m_davHelper.getNamespaceService();
|
|
}
|
|
|
|
/**
|
|
* @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();
|
|
}
|
|
|
|
/**
|
|
* Retrieve the (WebDAV protocol-level) locking service.
|
|
*
|
|
* @return WebDAVLockService
|
|
*/
|
|
protected final WebDAVLockService getDAVLockService()
|
|
{
|
|
return m_davHelper.getLockService();
|
|
}
|
|
|
|
/**
|
|
* Convenience method to return the action service
|
|
*
|
|
* @return ActionService
|
|
*/
|
|
protected final ActionService getActionService()
|
|
{
|
|
return m_davHelper.getActionService();
|
|
}
|
|
|
|
/**
|
|
* Convenience method to return the permission service
|
|
*
|
|
* @return PermissionService
|
|
*/
|
|
protected final PermissionService getPermissionService()
|
|
{
|
|
return m_davHelper.getPermissionService();
|
|
}
|
|
|
|
/**
|
|
* Convenience method to return the authentication service
|
|
*
|
|
* @return AuthenticationService
|
|
*/
|
|
protected final AuthenticationService getAuthenticationService()
|
|
{
|
|
return m_davHelper.getAuthenticationService();
|
|
}
|
|
|
|
/**
|
|
* @return Returns the path of the servlet, e.g. /webdav
|
|
*/
|
|
protected final String getServletPath()
|
|
{
|
|
return m_request.getServletPath();
|
|
}
|
|
|
|
/**
|
|
* @return Returns the context path of the servlet, e.g. /alfresco
|
|
*/
|
|
protected final String getContextPath()
|
|
{
|
|
return m_request.getContextPath();
|
|
}
|
|
|
|
/**
|
|
* Return the root node
|
|
*
|
|
* @return NodeRef
|
|
*/
|
|
protected final NodeRef getRootNodeRef()
|
|
{
|
|
return m_rootNodeRef;
|
|
}
|
|
|
|
/**
|
|
* Return the relative path
|
|
*
|
|
* @return String
|
|
*/
|
|
public String getPath()
|
|
{
|
|
return m_strPath;
|
|
}
|
|
|
|
/**
|
|
* Returns the format required for an XML response. This may vary per method.
|
|
*/
|
|
protected OutputFormat getXMLOutputFormat()
|
|
{
|
|
// Check if debug output or XML pretty printing is enabled
|
|
return (XMLPrettyPrint || logger.isDebugEnabled()) ? OutputFormat.createPrettyPrint() : OutputFormat.createCompactFormat();
|
|
}
|
|
|
|
/**
|
|
* Create an XML writer for the response
|
|
*
|
|
* @return XMLWriter
|
|
* @exception IOException
|
|
*/
|
|
protected final XMLWriter createXMLWriter() throws IOException
|
|
{
|
|
// Buffer the XML response, in case we have to reset mid-transaction
|
|
m_xmlWriter = new CharArrayWriter(1024);
|
|
return new XMLWriter(m_xmlWriter, getXMLOutputFormat());
|
|
}
|
|
|
|
/**
|
|
* Generates the lock discovery XML response
|
|
*
|
|
* @param xml XMLWriter
|
|
* @param lockNode NodeRef
|
|
* @param lockInfo
|
|
*/
|
|
protected void generateLockDiscoveryXML(XMLWriter xml, FileInfo lockNodeInfo, LockInfo lockInfo) throws Exception
|
|
{
|
|
String owner, scope, depth;
|
|
Date expiry;
|
|
|
|
lockInfo.getRWLock().readLock().lock();
|
|
try
|
|
{
|
|
owner = lockInfo.getOwner();
|
|
expiry = lockInfo.getExpires();
|
|
scope = lockInfo.getScope();
|
|
depth = lockInfo.getDepth();
|
|
}
|
|
finally
|
|
{
|
|
lockInfo.getRWLock().readLock().unlock();
|
|
}
|
|
|
|
generateLockDiscoveryXML(xml, lockNodeInfo, false, scope, depth,
|
|
WebDAV.makeLockToken(lockNodeInfo.getNodeRef(), owner), owner, expiry);
|
|
}
|
|
|
|
/**
|
|
* Generates the lock discovery XML response
|
|
*
|
|
* @param xml XMLWriter
|
|
* @param lockNode NodeRef
|
|
* @param emptyNamespace boolean True if namespace should be empty. Used to avoid bugs in WebDAV clients.
|
|
* @param scope String lock scope
|
|
* @param depth String lock depth
|
|
* @param lToken String locktoken
|
|
* @param owner String lock owner
|
|
* @param expiryDate the date/time the lock should expire
|
|
*/
|
|
protected void generateLockDiscoveryXML(XMLWriter xml, FileInfo lockNodeInfo, boolean emptyNamespace,
|
|
String scope, String depth, String lToken, String owner, Date expiryDate) throws Exception
|
|
{
|
|
Attributes nullAttr= getDAVHelper().getNullAttributes();
|
|
String ns = emptyNamespace ? "" : WebDAV.DAV_NS;
|
|
if (lockNodeInfo != null)
|
|
{
|
|
// Output the XML response
|
|
|
|
xml.startElement(ns, WebDAV.XML_LOCK_DISCOVERY, emptyNamespace ? WebDAV.XML_LOCK_DISCOVERY : WebDAV.XML_NS_LOCK_DISCOVERY, nullAttr);
|
|
xml.startElement(ns, WebDAV.XML_ACTIVE_LOCK, emptyNamespace ? WebDAV.XML_ACTIVE_LOCK : WebDAV.XML_NS_ACTIVE_LOCK, nullAttr);
|
|
|
|
xml.startElement(ns, WebDAV.XML_LOCK_TYPE, emptyNamespace ? WebDAV.XML_LOCK_TYPE : WebDAV.XML_NS_LOCK_TYPE, nullAttr);
|
|
xml.write(DocumentHelper.createElement(emptyNamespace ? WebDAV.XML_WRITE : WebDAV.XML_NS_WRITE));
|
|
xml.endElement(ns, WebDAV.XML_LOCK_TYPE, emptyNamespace ? WebDAV.XML_LOCK_TYPE : WebDAV.XML_NS_LOCK_TYPE);
|
|
|
|
xml.startElement(ns, WebDAV.XML_LOCK_SCOPE, emptyNamespace ? WebDAV.XML_LOCK_SCOPE : WebDAV.XML_NS_LOCK_SCOPE, nullAttr);
|
|
xml.write(DocumentHelper.createElement(emptyNamespace ? scope : WebDAV.DAV_NS_PREFIX + scope));
|
|
xml.endElement(ns, WebDAV.XML_LOCK_SCOPE, emptyNamespace ? WebDAV.XML_LOCK_SCOPE : WebDAV.XML_NS_LOCK_SCOPE);
|
|
|
|
// NOTE: We only support one level of lock at the moment
|
|
|
|
xml.startElement(ns, WebDAV.XML_DEPTH, emptyNamespace ? WebDAV.XML_DEPTH : WebDAV.XML_NS_DEPTH, nullAttr);
|
|
xml.write(depth);
|
|
xml.endElement(ns, WebDAV.XML_DEPTH, emptyNamespace ? WebDAV.XML_DEPTH : WebDAV.XML_NS_DEPTH);
|
|
|
|
xml.startElement(ns, WebDAV.XML_OWNER, emptyNamespace ? WebDAV.XML_OWNER : WebDAV.XML_NS_OWNER, nullAttr);
|
|
xml.write(owner);
|
|
xml.endElement(ns, WebDAV.XML_OWNER, emptyNamespace ? WebDAV.XML_OWNER : WebDAV.XML_NS_OWNER);
|
|
|
|
xml.startElement(ns, WebDAV.XML_TIMEOUT, emptyNamespace ? 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(ns, WebDAV.XML_TIMEOUT, emptyNamespace ? WebDAV.XML_TIMEOUT : WebDAV.XML_NS_TIMEOUT);
|
|
|
|
xml.startElement(ns, WebDAV.XML_LOCK_TOKEN, emptyNamespace ? WebDAV.XML_LOCK_TOKEN : WebDAV.XML_NS_LOCK_TOKEN, nullAttr);
|
|
xml.startElement(ns, WebDAV.XML_HREF, emptyNamespace ? WebDAV.XML_HREF : WebDAV.XML_NS_HREF, nullAttr);
|
|
|
|
xml.write(lToken);
|
|
|
|
xml.endElement(ns, WebDAV.XML_HREF, emptyNamespace ? WebDAV.XML_HREF : WebDAV.XML_NS_HREF);
|
|
xml.endElement(ns, WebDAV.XML_LOCK_TOKEN, emptyNamespace ? WebDAV.XML_LOCK_TOKEN : WebDAV.XML_NS_LOCK_TOKEN);
|
|
|
|
xml.endElement(ns, WebDAV.XML_ACTIVE_LOCK, emptyNamespace ? WebDAV.XML_ACTIVE_LOCK : WebDAV.XML_NS_ACTIVE_LOCK);
|
|
xml.endElement(ns, WebDAV.XML_LOCK_DISCOVERY, emptyNamespace ? WebDAV.XML_LOCK_DISCOVERY : WebDAV.XML_NS_LOCK_DISCOVERY);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates a list of namespace declarations for the response
|
|
*/
|
|
protected String generateNamespaceDeclarations(HashMap<String,String> 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<String> 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 == null ? "" : strNamespaceUri).append("\" ");
|
|
}
|
|
}
|
|
|
|
return ns.toString();
|
|
}
|
|
|
|
protected void setHidden(NodeRef nodeRef, boolean isHidden)
|
|
{
|
|
int mask = 0;
|
|
boolean allVisible = true;
|
|
Visibility webDavVisibility = isHidden ? Visibility.NotVisible : Visibility.Visible;
|
|
HiddenAspect hiddenAspect = m_davHelper.getHiddenAspect();
|
|
for (Client client : hiddenAspect.getClients())
|
|
{
|
|
Visibility clientVisibility = client == FileFilterMode.getClient() ? webDavVisibility : hiddenAspect
|
|
.getVisibility(client, nodeRef);
|
|
if (clientVisibility != Visibility.Visible)
|
|
{
|
|
allVisible = false;
|
|
}
|
|
mask |= hiddenAspect.getClientVisibilityMask(client, clientVisibility);
|
|
}
|
|
if (allVisible)
|
|
{
|
|
getNodeService().removeAspect(nodeRef, ContentModel.ASPECT_HIDDEN);
|
|
}
|
|
else
|
|
{
|
|
hiddenAspect.hideNode(nodeRef, mask);
|
|
}
|
|
}
|
|
|
|
protected boolean isHidden(NodeRef nodeRef)
|
|
{
|
|
return m_davHelper.getHiddenAspect().getVisibility(FileFilterMode.getClient(), nodeRef) != Visibility.Visible;
|
|
}
|
|
|
|
/**
|
|
* Checks if write operation can be performed on node.
|
|
*
|
|
* @param fileInfo - node's file info
|
|
* @param ignoreShared - if true ignores shared locks
|
|
* @param lockMethod - must be true if used from lock method
|
|
* @return node's lock info
|
|
* @throws WebDAVServerException if node has shared or exclusive lock
|
|
* or If header preconditions failed
|
|
*/
|
|
protected LockInfo checkNode(FileInfo fileInfo, boolean ignoreShared, boolean lockMethod) throws WebDAVServerException
|
|
{
|
|
LockInfo nodeLockInfo = getNodeLockInfo(fileInfo);
|
|
nodeLockInfo.getRWLock().readLock().lock();
|
|
try
|
|
{
|
|
String nodeETag = getDAVHelper().makeQuotedETag(fileInfo);
|
|
NodeRef nodeRef = fileInfo.getNodeRef();
|
|
|
|
// Regardless of WebDAV locks, if we can't write to this node, then it's locked!
|
|
if (getDAVHelper().isLockedAndReadOnly(nodeRef))
|
|
{
|
|
throw new WebDAVServerException(WebDAV.WEBDAV_SC_LOCKED);
|
|
}
|
|
|
|
// Handle the case where there are no conditions and no lock token stored on the node. Node just needs to be writable with no shared locks
|
|
if (m_conditions == null)
|
|
{
|
|
// ALF-3681 fix. WebDrive 10 client doesn't send If header when locked resource is updated so check the node by lockOwner.
|
|
if (!nodeLockInfo.isExclusive() || (m_userAgent != null && m_userAgent.equals(WebDAV.AGENT_MICROSOFT_DATA_ACCESS_INTERNET_PUBLISHING_PROVIDER_DAV)))
|
|
{
|
|
if (!ignoreShared && nodeLockInfo.isShared() && !nodeLockInfo.getSharedLockTokens().isEmpty())
|
|
{
|
|
throw new WebDAVServerException(WebDAV.WEBDAV_SC_LOCKED);
|
|
}
|
|
return nodeLockInfo;
|
|
}
|
|
}
|
|
|
|
// Checking of the If tag consists of two checks:
|
|
// 1. Check the appropriate lock token for the node has been supplied (if the node is locked)
|
|
// 2. If there are conditions, check at least one condition (corresponding to this node) is satisfied.
|
|
checkLockToken(nodeLockInfo, ignoreShared, lockMethod);
|
|
checkConditions(nodeLockInfo.getExclusiveLockToken(), nodeETag);
|
|
|
|
return nodeLockInfo;
|
|
}
|
|
finally
|
|
{
|
|
nodeLockInfo.getRWLock().readLock().unlock();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if write operation can be performed on node.
|
|
*
|
|
* @param fileInfo
|
|
* @return
|
|
* @throws WebDAVServerException if node has shared or exclusive lock
|
|
* or If header preconditions failed
|
|
*/
|
|
protected LockInfo checkNode(FileInfo fileInfo) throws WebDAVServerException
|
|
{
|
|
return checkNode(fileInfo, false, true);
|
|
}
|
|
|
|
/**
|
|
* Checks if node can be accessed with WebDAV operation
|
|
*
|
|
* @param lockInfo - node's lock info
|
|
* @param ignoreShared - if true - ignores shared lock tokens
|
|
* @param lockMethod - must be true if used from lock method
|
|
* @throws WebDAVServerException if node has no appropriate lock token
|
|
*/
|
|
private void checkLockToken(LockInfo lockInfo, boolean ignoreShared, boolean lockMethod) throws WebDAVServerException
|
|
{
|
|
lockInfo.getRWLock().readLock().lock();
|
|
try
|
|
{
|
|
String nodeLockToken = lockInfo.getExclusiveLockToken();
|
|
Set<String> sharedLockTokens = lockInfo.getSharedLockTokens();
|
|
|
|
if (m_conditions != null)
|
|
{
|
|
// Request has conditions to check
|
|
if (lockInfo.isShared())
|
|
{
|
|
// Node has shared lock. Check if conditions contains lock token of the node.
|
|
// If not throw exception
|
|
if (!sharedLockTokens.isEmpty())
|
|
{
|
|
if (!ignoreShared)
|
|
{
|
|
for (Condition condition : m_conditions)
|
|
{
|
|
for (String sharedLockToken : sharedLockTokens)
|
|
{
|
|
if (condition.getLockTokensMatch().contains(sharedLockToken))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
throw new WebDAVServerException(WebDAV.WEBDAV_SC_LOCKED);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// This particular node is not locked so it will not be part of the conditions
|
|
if (nodeLockToken == null)
|
|
{
|
|
return;
|
|
}
|
|
// Node has exclusive lock. Check if conditions contains lock token of the node
|
|
// If not throw exception
|
|
for (Condition condition : m_conditions)
|
|
{
|
|
if (condition.getLockTokensMatch().contains(nodeLockToken))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
throw new WebDAVServerException(WebDAV.WEBDAV_SC_LOCKED);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Request has no conditions
|
|
if (lockInfo.isShared())
|
|
{
|
|
// If lock is shared and check was called not from LOCK method return
|
|
if (!lockMethod)
|
|
{
|
|
return;
|
|
}
|
|
// Throw exception - we can't set lock on node with shared lock
|
|
throw new WebDAVServerException(WebDAV.WEBDAV_SC_LOCKED);
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
lockInfo.getRWLock().readLock().unlock();
|
|
}
|
|
|
|
throw new WebDAVServerException(WebDAV.WEBDAV_SC_LOCKED);
|
|
}
|
|
|
|
|
|
/**
|
|
* Checks If header conditions. Throws WebDAVServerException with 412(Precondition failed)
|
|
* if none of the conditions success.
|
|
*
|
|
* @param nodeLockToken - node's lock token
|
|
* @param nodeETag - node's ETag
|
|
* @throws WebDAVServerException if conditions fail
|
|
*/
|
|
private void checkConditions(String nodeLockToken, String nodeETag) throws WebDAVServerException
|
|
{
|
|
// Checks If header conditions.
|
|
// Each condition can contain check of ETag and check of Lock token.
|
|
|
|
if (m_conditions == null)
|
|
{
|
|
// No conditions were provided with "If" request header, so check successful
|
|
return;
|
|
}
|
|
|
|
// Check the list of "If" header's conditions.
|
|
// If any condition conforms then check is successful
|
|
for (Condition condition : m_conditions)
|
|
{
|
|
// Flag for ETag conditions
|
|
boolean fMatchETag = true;
|
|
// Flag for Lock token conditions
|
|
boolean fMatchLockToken = true;
|
|
|
|
// Check ETags that should match
|
|
if (condition.getETagsMatch() != null)
|
|
{
|
|
fMatchETag = condition.getETagsMatch().contains(nodeETag) ? true : false;
|
|
}
|
|
// Check ETags that shouldn't match
|
|
if (condition.getETagsNotMatch() != null)
|
|
{
|
|
fMatchETag = condition.getETagsNotMatch().contains(nodeETag) ? false : true;
|
|
}
|
|
// Check lock tokens that should match
|
|
if (condition.getLockTokensMatch() != null)
|
|
{
|
|
fMatchLockToken = nodeLockToken == null || condition.getLockTokensMatch().contains(nodeLockToken);
|
|
}
|
|
// Check lock tokens that shouldn't match
|
|
if (condition.getLockTokensNotMatch() != null)
|
|
{
|
|
fMatchLockToken = nodeLockToken == null || !condition.getLockTokensNotMatch().contains(nodeLockToken);
|
|
}
|
|
|
|
if (fMatchETag && fMatchLockToken)
|
|
{
|
|
// Condition conforms
|
|
return;
|
|
}
|
|
}
|
|
|
|
// None of the conditions successful
|
|
throw new WebDAVServerException(HttpServletResponse.SC_PRECONDITION_FAILED);
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns node Lock token in consideration of WebDav lock depth.
|
|
*
|
|
* @param fileInfo node
|
|
* @return String Lock token
|
|
*/
|
|
protected LockInfo getNodeLockInfo(final FileInfo nodeInfo)
|
|
{
|
|
// perf optimisation - effectively run against unprotected nodeService (to bypass repeated permission checks)
|
|
return AuthenticationUtil.runAs(new RunAsWork<LockInfo>()
|
|
{
|
|
public LockInfo doWork() throws Exception
|
|
{
|
|
return getNodeLockInfoImpl(nodeInfo);
|
|
}
|
|
}, AuthenticationUtil.getSystemUserName());
|
|
}
|
|
|
|
private LockInfo getNodeLockInfoImpl(final FileInfo nodeInfo)
|
|
{
|
|
// Check if node is locked directly.
|
|
LockInfo lockInfo = getNodeLockInfoDirect(nodeInfo);
|
|
if (lockInfo != null)
|
|
{
|
|
return lockInfo;
|
|
}
|
|
|
|
// Node isn't locked directly, try to search for an indirect lock.
|
|
|
|
// ALF-13472: In accordance with http://www.webdav.org/specs/rfc2518.html#rfc.section.8.10.4 lock of collection causes locking each resource within it.
|
|
// It should be possible to receive information about direct or indirect lock because it is one of the states of requested resource.
|
|
return AuthenticationUtil.runAsSystem(new RunAsWork<LockInfo>()
|
|
{
|
|
@Override
|
|
public LockInfo doWork() throws Exception
|
|
{
|
|
NodeService nodeService = getNodeService();
|
|
|
|
NodeRef node = nodeInfo.getNodeRef();
|
|
|
|
while (true)
|
|
{
|
|
NodeRef parent = m_childToParent.get(node);
|
|
|
|
if ((parent == null) && (! m_childToParent.containsKey(node)))
|
|
{
|
|
ChildAssociationRef childAssocRef = nodeService.getPrimaryParent(node);
|
|
parent = childAssocRef.getParentRef();
|
|
|
|
if (! childAssocRef.getTypeQName().equals(ContentModel.ASSOC_CONTAINS))
|
|
{
|
|
parent = null;
|
|
}
|
|
|
|
// temporarily cache - for this request
|
|
m_childToParent.put(node, parent);
|
|
}
|
|
|
|
if (parent == null)
|
|
{
|
|
// Node has no lock and Lock token
|
|
return new LockInfoImpl();
|
|
}
|
|
|
|
LockInfo lockInfo = m_parentLockInfo.get(parent);
|
|
|
|
if (lockInfo != null)
|
|
{
|
|
lockInfo.getRWLock().readLock().lock();
|
|
try
|
|
{
|
|
if (lockInfo.isLocked())
|
|
{
|
|
return lockInfo;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
lockInfo.getRWLock().readLock().unlock();
|
|
}
|
|
}
|
|
|
|
if (lockInfo == null)
|
|
{
|
|
try
|
|
{
|
|
lockInfo = getNodeLockInfoIndirect(parent);
|
|
if (lockInfo != null)
|
|
{
|
|
return lockInfo;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (lockInfo == null)
|
|
{
|
|
lockInfo = new LockInfoImpl();
|
|
}
|
|
// temporarily cache - for this request
|
|
m_parentLockInfo.put(parent, lockInfo);
|
|
}
|
|
}
|
|
|
|
node = parent;
|
|
} // end while
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
/**
|
|
* Checks if a node is directly locked. A direct lock is one associated with the node itself
|
|
* rather than associated with some ancestor.
|
|
*
|
|
* @param nodeInfo
|
|
* @return The LockInfo if the node is <strong>locked</strong>, or null otherwise.
|
|
*/
|
|
private LockInfo getNodeLockInfoDirect(FileInfo nodeInfo)
|
|
{
|
|
LockInfo lock = getDAVLockService().getLockInfo(nodeInfo.getNodeRef());
|
|
|
|
if (lock == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
lock.getRWLock().readLock().lock();
|
|
try
|
|
{
|
|
if (lock.isLocked())
|
|
{
|
|
return lock;
|
|
}
|
|
return null;
|
|
}
|
|
finally
|
|
{
|
|
lock.getRWLock().readLock().unlock();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks whether a parent node has a lock that is valid for all its descendants.
|
|
*
|
|
* @param parent
|
|
* @return The LockInfo if the node is <strong>locked</strong>, or null otherwise.
|
|
*/
|
|
private LockInfo getNodeLockInfoIndirect(NodeRef parent)
|
|
{
|
|
LockInfo parentLock = getDAVLockService().getLockInfo(parent);
|
|
|
|
if (parentLock == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
parentLock.getRWLock().readLock().lock();
|
|
try
|
|
{
|
|
// In this case node is locked indirectly.
|
|
if (parentLock.isLocked() && WebDAV.INFINITY.equals(parentLock.getDepth()))
|
|
{
|
|
// In this case node is locked indirectly.
|
|
//Get lock scope
|
|
// Get shared lock tokens
|
|
|
|
// Store lock information to the lockInfo object
|
|
|
|
// Get lock token of the locked node - this is indirect lock token.
|
|
|
|
return parentLock;
|
|
}
|
|
return null;
|
|
}
|
|
// No has no exclusive lock but can be locked with shared lock
|
|
// Check node lock depth.
|
|
// If depth is WebDAV.INFINITY then return this node's Lock token.
|
|
// In this case node is locked indirectly.
|
|
|
|
//Get lock scope
|
|
|
|
// Node has it's own Lock token.
|
|
finally
|
|
{
|
|
parentLock.getRWLock().readLock().unlock();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
protected FileInfo getNodeForPath(NodeRef rootNodeRef, String path, String servletPath) throws FileNotFoundException
|
|
{
|
|
return getDAVHelper().getNodeForPath(rootNodeRef, path, servletPath);
|
|
}
|
|
|
|
/**
|
|
* Returns a URL that could be used to access the given path.
|
|
*
|
|
* @param request HttpServletRequest
|
|
* @param path the path to search for
|
|
* @param isFolder indicates file or folder is requested
|
|
* @return URL that could be used to access the given path
|
|
*/
|
|
protected String getURLForPath(HttpServletRequest request, String path, boolean isFolder)
|
|
{
|
|
return getDAVHelper().getURLForPath(request, path, isFolder, m_userAgent);
|
|
}
|
|
|
|
/**
|
|
* Determines whether the XMLWriter should be flushed when XML is flushed. For some reason this is method specific.
|
|
* @return <code>true</code> if the XMLWriter should be flushed when XML is flushed
|
|
*/
|
|
protected boolean shouldFlushXMLWriter()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Flushes all XML written so far to the response
|
|
*
|
|
* @param xml XMLWriter that should be flushed
|
|
*/
|
|
protected final void flushXML(XMLWriter writer) throws IOException
|
|
{
|
|
if (shouldFlushXMLWriter())
|
|
{
|
|
writer.flush();
|
|
}
|
|
|
|
m_response.getWriter().write(m_xmlWriter.toCharArray());
|
|
|
|
m_xmlWriter.reset();
|
|
}
|
|
|
|
/**
|
|
* Returns a working copy of node for current user.
|
|
*
|
|
* @param nodeRef node reference
|
|
* @return Returns the working copy's file information
|
|
*/
|
|
protected FileInfo getWorkingCopy(NodeRef nodeRef)
|
|
{
|
|
FileInfo result = null;
|
|
NodeRef workingCopy = getServiceRegistry().getCheckOutCheckInService().getWorkingCopy(nodeRef);
|
|
if (workingCopy != null)
|
|
{
|
|
String workingCopyOwner = getNodeService().getProperty(workingCopy, ContentModel.PROP_WORKING_COPY_OWNER).toString();
|
|
if (workingCopyOwner.equals(getAuthenticationService().getCurrentUserName()))
|
|
{
|
|
result = getFileFolderService().getFileInfo(workingCopy);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Determines status code for AccessDeniedException based on client's HTTP headers.
|
|
*
|
|
* @return Returns status code
|
|
*/
|
|
protected int getStatusForAccessDeniedException()
|
|
{
|
|
if (m_request != null && m_request.getHeader(WebDAV.HEADER_USER_AGENT) != null)
|
|
{
|
|
String userAgent = m_request.getHeader(WebDAV.HEADER_USER_AGENT);
|
|
|
|
for (Entry<String, Integer> entry : accessDeniedStatusCodes.entrySet())
|
|
{
|
|
if (Pattern.compile(entry.getKey()).matcher(userAgent).find())
|
|
{
|
|
return entry.getValue();
|
|
}
|
|
}
|
|
}
|
|
return HttpServletResponse.SC_UNAUTHORIZED;
|
|
}
|
|
|
|
/**
|
|
* Class used for storing conditions which comes with "If" header of the request
|
|
*
|
|
* @author ivanry
|
|
*/
|
|
protected class Condition
|
|
{
|
|
// These tokens will be checked on equivalence against node's lock token
|
|
private LinkedList<String> lockTokensMatch = new LinkedList<String>();
|
|
|
|
// These tokens will be checked on non-equivalence against node's lock token
|
|
private LinkedList<String> lockTokensNotMatch = new LinkedList<String>();
|
|
|
|
// These ETags will be checked on equivalence against node's ETags
|
|
private LinkedList<String> eTagsMatch;
|
|
|
|
// These ETags will be checked on non-equivalence against node's ETags
|
|
private LinkedList<String> eTagsNotMatch;
|
|
|
|
/**
|
|
* Default constructor
|
|
*
|
|
*/
|
|
public Condition()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Returns the list of lock tokens that should be checked against node's lock token on equivalence.
|
|
*
|
|
* @return lock tokens
|
|
*/
|
|
public LinkedList<String> getLockTokensMatch()
|
|
{
|
|
return this.lockTokensMatch;
|
|
}
|
|
|
|
/**
|
|
* Returns the list of lock tokens that should be checked against node's lock token on non-equivalence.
|
|
*
|
|
* @return lock tokens
|
|
*/
|
|
public LinkedList<String> getLockTokensNotMatch()
|
|
{
|
|
return this.lockTokensNotMatch;
|
|
}
|
|
|
|
/**
|
|
* Returns the list of ETags that should be checked against node's ETag on equivalence.
|
|
*
|
|
* @return ETags list
|
|
*/
|
|
public LinkedList<String> getETagsMatch()
|
|
{
|
|
return this.eTagsMatch;
|
|
}
|
|
|
|
/**
|
|
* Returns the list of ETags that should be checked against node's ETag on non-equivalence.
|
|
*
|
|
* @return ETags list
|
|
*/
|
|
public LinkedList<String> getETagsNotMatch()
|
|
{
|
|
return this.eTagsNotMatch;
|
|
}
|
|
|
|
/**
|
|
* Adds lock token to check
|
|
*
|
|
* @param lockToken String
|
|
* @param notMatch true is lock token should be added to the list matched tokens.
|
|
* false if should be added to the list of non-matches.
|
|
*/
|
|
public void addLockTocken(String lockToken, boolean notMatch)
|
|
{
|
|
if (notMatch)
|
|
{
|
|
this.lockTokensNotMatch.add(lockToken);
|
|
}
|
|
else
|
|
{
|
|
this.lockTokensMatch.add(lockToken);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add ETag to check
|
|
*
|
|
* @param eTag String
|
|
* @param notMatch true is ETag should be added to the list matched ETags.
|
|
* false if should be added to the list of non-matches.
|
|
*/
|
|
public void addETag(String eTag, boolean notMatch)
|
|
{
|
|
if (notMatch)
|
|
{
|
|
if (eTagsNotMatch == null)
|
|
{
|
|
eTagsNotMatch = new LinkedList<String>();
|
|
}
|
|
this.eTagsNotMatch.add(eTag);
|
|
}
|
|
else
|
|
{
|
|
if (eTagsMatch == null)
|
|
{
|
|
eTagsMatch = new LinkedList<String>();
|
|
}
|
|
this.eTagsMatch.add(eTag);
|
|
}
|
|
}
|
|
}
|
|
|
|
public String toString()
|
|
{
|
|
StringBuffer sb = new StringBuffer();
|
|
|
|
if(m_request != null)
|
|
{
|
|
sb.append("WebDAV ");
|
|
sb.append(m_request.getMethod());
|
|
sb.append(" request for ");
|
|
sb.append(m_strPath);
|
|
}
|
|
else
|
|
{
|
|
sb.append("Inactive WebDAV request via ");
|
|
String clz = getClass().getName();
|
|
sb.append(clz.substring(clz.lastIndexOf('.')+1));
|
|
}
|
|
|
|
return sb.toString();
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the site ID (short-name) that the current request relates to. The site ID
|
|
* will be {@link DEFAULT_SITE_ID} if not specifically set.
|
|
*
|
|
* @return The site ID
|
|
*/
|
|
protected String getSiteId()
|
|
{
|
|
if (siteId == null)
|
|
{
|
|
siteId = getDAVHelper().determineSiteId(this);
|
|
}
|
|
return siteId;
|
|
}
|
|
|
|
/**
|
|
* Get the tenant domain for the current user and request. The tenant domain
|
|
* will be {@link TenantService#DEFAULT_DOMAIN} if not specifically set.
|
|
*
|
|
* @return The tenant domain.
|
|
*/
|
|
protected String getTenantDomain()
|
|
{
|
|
if (tenantDomain == null)
|
|
{
|
|
tenantDomain = getDAVHelper().determineTenantDomain(this);
|
|
}
|
|
return tenantDomain;
|
|
}
|
|
}
|