From 0b02a6d123b2014705998c94ddf3d460948d3eac Mon Sep 17 00:00:00 2001 From: redmatrix Date: Tue, 10 May 2016 17:26:44 -0700 Subject: initial sabre upgrade (needs lots of work - to wit: authentication, redo the browser interface, and rework event export/import) --- .../dav/lib/CalDAV/Backend/AbstractBackend.php | 226 ++ .../dav/lib/CalDAV/Backend/BackendInterface.php | 268 +++ .../dav/lib/CalDAV/Backend/NotificationSupport.php | 46 + vendor/sabre/dav/lib/CalDAV/Backend/PDO.php | 1210 +++++++++++ .../dav/lib/CalDAV/Backend/SchedulingSupport.php | 65 + .../dav/lib/CalDAV/Backend/SharingSupport.php | 243 +++ .../dav/lib/CalDAV/Backend/SubscriptionSupport.php | 89 + .../sabre/dav/lib/CalDAV/Backend/SyncSupport.php | 81 + vendor/sabre/dav/lib/CalDAV/Calendar.php | 527 +++++ vendor/sabre/dav/lib/CalDAV/CalendarHome.php | 430 ++++ vendor/sabre/dav/lib/CalDAV/CalendarObject.php | 290 +++ .../dav/lib/CalDAV/CalendarQueryValidator.php | 375 ++++ vendor/sabre/dav/lib/CalDAV/CalendarRoot.php | 80 + .../lib/CalDAV/Exception/InvalidComponentType.php | 35 + vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php | 366 ++++ vendor/sabre/dav/lib/CalDAV/ICalendar.php | 18 + vendor/sabre/dav/lib/CalDAV/ICalendarObject.php | 21 + .../dav/lib/CalDAV/ICalendarObjectContainer.php | 39 + vendor/sabre/dav/lib/CalDAV/IShareableCalendar.php | 48 + vendor/sabre/dav/lib/CalDAV/ISharedCalendar.php | 36 + .../dav/lib/CalDAV/Notifications/Collection.php | 173 ++ .../dav/lib/CalDAV/Notifications/ICollection.php | 23 + .../sabre/dav/lib/CalDAV/Notifications/INode.php | 38 + vendor/sabre/dav/lib/CalDAV/Notifications/Node.php | 193 ++ .../sabre/dav/lib/CalDAV/Notifications/Plugin.php | 180 ++ vendor/sabre/dav/lib/CalDAV/Plugin.php | 1025 +++++++++ .../sabre/dav/lib/CalDAV/Principal/Collection.php | 33 + .../sabre/dav/lib/CalDAV/Principal/IProxyRead.php | 19 + .../sabre/dav/lib/CalDAV/Principal/IProxyWrite.php | 19 + .../sabre/dav/lib/CalDAV/Principal/ProxyRead.php | 181 ++ .../sabre/dav/lib/CalDAV/Principal/ProxyWrite.php | 181 ++ vendor/sabre/dav/lib/CalDAV/Principal/User.php | 135 ++ vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php | 15 + .../sabre/dav/lib/CalDAV/Schedule/IMipPlugin.php | 190 ++ vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php | 15 + .../dav/lib/CalDAV/Schedule/ISchedulingObject.php | 13 + vendor/sabre/dav/lib/CalDAV/Schedule/Inbox.php | 261 +++ vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php | 184 ++ vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php | 994 +++++++++ .../dav/lib/CalDAV/Schedule/SchedulingObject.php | 165 ++ vendor/sabre/dav/lib/CalDAV/ShareableCalendar.php | 72 + vendor/sabre/dav/lib/CalDAV/SharedCalendar.php | 148 ++ vendor/sabre/dav/lib/CalDAV/SharingPlugin.php | 426 ++++ .../dav/lib/CalDAV/Subscriptions/ISubscription.php | 40 + .../sabre/dav/lib/CalDAV/Subscriptions/Plugin.php | 120 ++ .../dav/lib/CalDAV/Subscriptions/Subscription.php | 274 +++ .../dav/lib/CalDAV/Xml/Filter/CalendarData.php | 84 + .../sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php | 97 + .../dav/lib/CalDAV/Xml/Filter/ParamFilter.php | 82 + .../sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php | 98 + .../dav/lib/CalDAV/Xml/Notification/Invite.php | 307 +++ .../lib/CalDAV/Xml/Notification/InviteReply.php | 212 ++ .../Xml/Notification/NotificationInterface.php | 45 + .../lib/CalDAV/Xml/Notification/SystemStatus.php | 182 ++ .../CalDAV/Xml/Property/AllowedSharingModes.php | 87 + .../lib/CalDAV/Xml/Property/EmailAddressSet.php | 80 + .../sabre/dav/lib/CalDAV/Xml/Property/Invite.php | 252 +++ .../CalDAV/Xml/Property/ScheduleCalendarTransp.php | 140 ++ .../Xml/Property/SupportedCalendarComponentSet.php | 129 ++ .../CalDAV/Xml/Property/SupportedCalendarData.php | 60 + .../CalDAV/Xml/Property/SupportedCollationSet.php | 57 + .../CalDAV/Xml/Request/CalendarMultiGetReport.php | 124 ++ .../lib/CalDAV/Xml/Request/CalendarQueryReport.php | 139 ++ .../lib/CalDAV/Xml/Request/FreeBusyQueryReport.php | 91 + .../dav/lib/CalDAV/Xml/Request/InviteReply.php | 149 ++ .../dav/lib/CalDAV/Xml/Request/MkCalendar.php | 79 + vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php | 116 ++ vendor/sabre/dav/lib/CardDAV/AddressBook.php | 433 ++++ vendor/sabre/dav/lib/CardDAV/AddressBookHome.php | 263 +++ vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php | 80 + .../dav/lib/CardDAV/Backend/AbstractBackend.php | 38 + .../dav/lib/CardDAV/Backend/BackendInterface.php | 187 ++ vendor/sabre/dav/lib/CardDAV/Backend/PDO.php | 545 +++++ .../sabre/dav/lib/CardDAV/Backend/SyncSupport.php | 81 + vendor/sabre/dav/lib/CardDAV/Card.php | 263 +++ vendor/sabre/dav/lib/CardDAV/IAddressBook.php | 18 + vendor/sabre/dav/lib/CardDAV/ICard.php | 19 + vendor/sabre/dav/lib/CardDAV/IDirectory.php | 20 + vendor/sabre/dav/lib/CardDAV/Plugin.php | 871 ++++++++ vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php | 152 ++ .../dav/lib/CardDAV/Xml/Filter/AddressData.php | 59 + .../dav/lib/CardDAV/Xml/Filter/ParamFilter.php | 89 + .../dav/lib/CardDAV/Xml/Filter/PropFilter.php | 98 + .../CardDAV/Xml/Property/SupportedAddressData.php | 83 + .../CardDAV/Xml/Property/SupportedCollationSet.php | 47 + .../Xml/Request/AddressBookMultiGetReport.php | 113 + .../CardDAV/Xml/Request/AddressBookQueryReport.php | 192 ++ .../dav/lib/DAV/Auth/Backend/AbstractBasic.php | 144 ++ .../dav/lib/DAV/Auth/Backend/AbstractBearer.php | 138 ++ .../dav/lib/DAV/Auth/Backend/AbstractDigest.php | 162 ++ vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php | 96 + .../dav/lib/DAV/Auth/Backend/BackendInterface.php | 70 + .../dav/lib/DAV/Auth/Backend/BasicCallBack.php | 58 + vendor/sabre/dav/lib/DAV/Auth/Backend/File.php | 77 + vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php | 57 + vendor/sabre/dav/lib/DAV/Auth/Plugin.php | 213 ++ .../sabre/dav/lib/DAV/Browser/GuessContentType.php | 101 + vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php | 34 + .../sabre/dav/lib/DAV/Browser/HtmlOutputHelper.php | 117 ++ .../sabre/dav/lib/DAV/Browser/MapGetToPropFind.php | 60 + vendor/sabre/dav/lib/DAV/Browser/Plugin.php | 797 +++++++ vendor/sabre/dav/lib/DAV/Browser/PropFindAll.php | 132 ++ .../lib/DAV/Browser/assets/openiconic/ICON-LICENSE | 21 + .../DAV/Browser/assets/openiconic/open-iconic.css | 510 +++++ .../DAV/Browser/assets/openiconic/open-iconic.eot | Bin 0 -> 23144 bytes .../DAV/Browser/assets/openiconic/open-iconic.otf | Bin 0 -> 21048 bytes .../DAV/Browser/assets/openiconic/open-iconic.svg | 543 +++++ .../DAV/Browser/assets/openiconic/open-iconic.ttf | Bin 0 -> 25568 bytes .../DAV/Browser/assets/openiconic/open-iconic.woff | Bin 0 -> 12404 bytes .../sabre/dav/lib/DAV/Browser/assets/sabredav.css | 228 ++ .../sabre/dav/lib/DAV/Browser/assets/sabredav.png | Bin 0 -> 2825 bytes vendor/sabre/dav/lib/DAV/Client.php | 448 ++++ vendor/sabre/dav/lib/DAV/Collection.php | 109 + vendor/sabre/dav/lib/DAV/CorePlugin.php | 927 +++++++++ vendor/sabre/dav/lib/DAV/Exception.php | 57 + vendor/sabre/dav/lib/DAV/Exception/BadRequest.php | 30 + vendor/sabre/dav/lib/DAV/Exception/Conflict.php | 30 + .../dav/lib/DAV/Exception/ConflictingLock.php | 36 + vendor/sabre/dav/lib/DAV/Exception/Forbidden.php | 29 + .../dav/lib/DAV/Exception/InsufficientStorage.php | 29 + .../dav/lib/DAV/Exception/InvalidResourceType.php | 33 + .../dav/lib/DAV/Exception/InvalidSyncToken.php | 38 + .../sabre/dav/lib/DAV/Exception/LengthRequired.php | 30 + .../DAV/Exception/LockTokenMatchesRequestUri.php | 41 + vendor/sabre/dav/lib/DAV/Exception/Locked.php | 72 + .../dav/lib/DAV/Exception/MethodNotAllowed.php | 47 + .../dav/lib/DAV/Exception/NotAuthenticated.php | 30 + vendor/sabre/dav/lib/DAV/Exception/NotFound.php | 29 + .../sabre/dav/lib/DAV/Exception/NotImplemented.php | 29 + .../dav/lib/DAV/Exception/PaymentRequired.php | 30 + .../dav/lib/DAV/Exception/PreconditionFailed.php | 71 + .../dav/lib/DAV/Exception/ReportNotSupported.php | 32 + .../DAV/Exception/RequestedRangeNotSatisfiable.php | 30 + .../dav/lib/DAV/Exception/ServiceUnavailable.php | 30 + .../sabre/dav/lib/DAV/Exception/TooManyMatches.php | 38 + .../dav/lib/DAV/Exception/UnsupportedMediaType.php | 30 + vendor/sabre/dav/lib/DAV/FS/Directory.php | 151 ++ vendor/sabre/dav/lib/DAV/FS/File.php | 95 + vendor/sabre/dav/lib/DAV/FS/Node.php | 80 + vendor/sabre/dav/lib/DAV/FSExt/Directory.php | 219 ++ vendor/sabre/dav/lib/DAV/FSExt/File.php | 152 ++ vendor/sabre/dav/lib/DAV/File.php | 84 + vendor/sabre/dav/lib/DAV/ICollection.php | 76 + vendor/sabre/dav/lib/DAV/IExtendedCollection.php | 43 + vendor/sabre/dav/lib/DAV/IFile.php | 81 + vendor/sabre/dav/lib/DAV/IMoveTarget.php | 44 + vendor/sabre/dav/lib/DAV/IMultiGet.php | 36 + vendor/sabre/dav/lib/DAV/INode.php | 45 + vendor/sabre/dav/lib/DAV/IProperties.php | 47 + vendor/sabre/dav/lib/DAV/IQuota.php | 26 + .../dav/lib/DAV/Locks/Backend/AbstractBackend.php | 18 + .../dav/lib/DAV/Locks/Backend/BackendInterface.php | 50 + vendor/sabre/dav/lib/DAV/Locks/Backend/File.php | 185 ++ vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php | 180 ++ vendor/sabre/dav/lib/DAV/Locks/LockInfo.php | 80 + vendor/sabre/dav/lib/DAV/Locks/Plugin.php | 589 ++++++ vendor/sabre/dav/lib/DAV/MkCol.php | 71 + vendor/sabre/dav/lib/DAV/Mount/Plugin.php | 86 + vendor/sabre/dav/lib/DAV/Node.php | 54 + .../dav/lib/DAV/PartialUpdate/IPatchSupport.php | 47 + vendor/sabre/dav/lib/DAV/PartialUpdate/Plugin.php | 215 ++ vendor/sabre/dav/lib/DAV/PropFind.php | 347 ++++ vendor/sabre/dav/lib/DAV/PropPatch.php | 373 ++++ .../PropertyStorage/Backend/BackendInterface.php | 80 + .../dav/lib/DAV/PropertyStorage/Backend/PDO.php | 217 ++ .../sabre/dav/lib/DAV/PropertyStorage/Plugin.php | 180 ++ vendor/sabre/dav/lib/DAV/Server.php | 1627 +++++++++++++++ vendor/sabre/dav/lib/DAV/ServerPlugin.php | 110 + vendor/sabre/dav/lib/DAV/SimpleCollection.php | 107 + vendor/sabre/dav/lib/DAV/SimpleFile.php | 121 ++ vendor/sabre/dav/lib/DAV/StringUtil.php | 91 + vendor/sabre/dav/lib/DAV/Sync/ISyncCollection.php | 88 + vendor/sabre/dav/lib/DAV/Sync/Plugin.php | 277 +++ .../dav/lib/DAV/TemporaryFileFilterPlugin.php | 297 +++ vendor/sabre/dav/lib/DAV/Tree.php | 340 +++ vendor/sabre/dav/lib/DAV/UUIDUtil.php | 64 + vendor/sabre/dav/lib/DAV/Version.php | 19 + vendor/sabre/dav/lib/DAV/Xml/Element/Prop.php | 116 ++ vendor/sabre/dav/lib/DAV/Xml/Element/Response.php | 253 +++ vendor/sabre/dav/lib/DAV/Xml/Property/Complex.php | 89 + .../dav/lib/DAV/Xml/Property/GetLastModified.php | 110 + vendor/sabre/dav/lib/DAV/Xml/Property/Href.php | 176 ++ .../dav/lib/DAV/Xml/Property/LockDiscovery.php | 106 + .../dav/lib/DAV/Xml/Property/ResourceType.php | 128 ++ .../dav/lib/DAV/Xml/Property/SupportedLock.php | 54 + .../lib/DAV/Xml/Property/SupportedMethodSet.php | 126 ++ .../lib/DAV/Xml/Property/SupportedReportSet.php | 154 ++ vendor/sabre/dav/lib/DAV/Xml/Request/Lock.php | 81 + vendor/sabre/dav/lib/DAV/Xml/Request/MkCol.php | 82 + vendor/sabre/dav/lib/DAV/Xml/Request/PropFind.php | 83 + vendor/sabre/dav/lib/DAV/Xml/Request/PropPatch.php | 118 ++ .../lib/DAV/Xml/Request/SyncCollectionReport.php | 122 ++ .../sabre/dav/lib/DAV/Xml/Response/MultiStatus.php | 142 ++ vendor/sabre/dav/lib/DAV/Xml/Service.php | 47 + .../dav/lib/DAVACL/AbstractPrincipalCollection.php | 181 ++ .../sabre/dav/lib/DAVACL/Exception/AceConflict.php | 35 + .../dav/lib/DAVACL/Exception/NeedPrivileges.php | 82 + .../sabre/dav/lib/DAVACL/Exception/NoAbstract.php | 35 + .../DAVACL/Exception/NotRecognizedPrincipal.php | 35 + .../lib/DAVACL/Exception/NotSupportedPrivilege.php | 35 + vendor/sabre/dav/lib/DAVACL/FS/Collection.php | 153 ++ vendor/sabre/dav/lib/DAVACL/FS/File.php | 123 ++ vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php | 188 ++ vendor/sabre/dav/lib/DAVACL/IACL.php | 75 + vendor/sabre/dav/lib/DAVACL/IPrincipal.php | 77 + .../sabre/dav/lib/DAVACL/IPrincipalCollection.php | 62 + vendor/sabre/dav/lib/DAVACL/Plugin.php | 1323 ++++++++++++ vendor/sabre/dav/lib/DAVACL/Principal.php | 288 +++ .../DAVACL/PrincipalBackend/AbstractBackend.php | 53 + .../DAVACL/PrincipalBackend/BackendInterface.php | 141 ++ .../PrincipalBackend/CreatePrincipalSupport.php | 30 + .../sabre/dav/lib/DAVACL/PrincipalBackend/PDO.php | 431 ++++ .../sabre/dav/lib/DAVACL/PrincipalCollection.php | 151 ++ vendor/sabre/dav/lib/DAVACL/Xml/Property/Acl.php | 277 +++ .../lib/DAVACL/Xml/Property/AclRestrictions.php | 45 + .../Xml/Property/CurrentUserPrivilegeSet.php | 159 ++ .../dav/lib/DAVACL/Xml/Property/Principal.php | 196 ++ .../DAVACL/Xml/Property/SupportedPrivilegeSet.php | 159 ++ .../DAVACL/Xml/Request/ExpandPropertyReport.php | 103 + .../Xml/Request/PrincipalPropertySearchReport.php | 127 ++ .../Request/PrincipalSearchPropertySetReport.php | 58 + .../lib/Sabre/CalDAV/Backend/AbstractBackend.php | 155 -- .../lib/Sabre/CalDAV/Backend/BackendInterface.php | 233 --- .../Sabre/CalDAV/Backend/NotificationSupport.php | 47 - vendor/sabre/dav/lib/Sabre/CalDAV/Backend/PDO.php | 691 ------- .../lib/Sabre/CalDAV/Backend/SharingSupport.php | 243 --- vendor/sabre/dav/lib/Sabre/CalDAV/Calendar.php | 376 ---- .../sabre/dav/lib/Sabre/CalDAV/CalendarObject.php | 279 --- .../dav/lib/Sabre/CalDAV/CalendarQueryParser.php | 298 --- .../lib/Sabre/CalDAV/CalendarQueryValidator.php | 392 ---- .../dav/lib/Sabre/CalDAV/CalendarRootNode.php | 77 - .../CalDAV/Exception/InvalidComponentType.php | 35 - .../sabre/dav/lib/Sabre/CalDAV/ICSExportPlugin.php | 142 -- vendor/sabre/dav/lib/Sabre/CalDAV/ICalendar.php | 36 - .../sabre/dav/lib/Sabre/CalDAV/ICalendarObject.php | 21 - .../dav/lib/Sabre/CalDAV/IShareableCalendar.php | 48 - .../sabre/dav/lib/Sabre/CalDAV/ISharedCalendar.php | 36 - .../lib/Sabre/CalDAV/Notifications/Collection.php | 173 -- .../lib/Sabre/CalDAV/Notifications/ICollection.php | 24 - .../dav/lib/Sabre/CalDAV/Notifications/INode.php | 38 - .../CalDAV/Notifications/INotificationType.php | 44 - .../dav/lib/Sabre/CalDAV/Notifications/Node.php | 192 -- .../CalDAV/Notifications/Notification/Invite.php | 324 --- .../Notifications/Notification/InviteReply.php | 218 -- .../Notifications/Notification/SystemStatus.php | 182 -- vendor/sabre/dav/lib/Sabre/CalDAV/Plugin.php | 1338 ------------ .../dav/lib/Sabre/CalDAV/Principal/Collection.php | 32 - .../dav/lib/Sabre/CalDAV/Principal/IProxyRead.php | 19 - .../dav/lib/Sabre/CalDAV/Principal/IProxyWrite.php | 19 - .../dav/lib/Sabre/CalDAV/Principal/ProxyRead.php | 180 -- .../dav/lib/Sabre/CalDAV/Principal/ProxyWrite.php | 180 -- .../sabre/dav/lib/Sabre/CalDAV/Principal/User.php | 134 -- .../Sabre/CalDAV/Property/AllowedSharingModes.php | 74 - .../sabre/dav/lib/Sabre/CalDAV/Property/Invite.php | 227 -- .../CalDAV/Property/ScheduleCalendarTransp.php | 102 - .../Property/SupportedCalendarComponentSet.php | 88 - .../CalDAV/Property/SupportedCalendarData.php | 40 - .../CalDAV/Property/SupportedCollationSet.php | 45 - .../sabre/dav/lib/Sabre/CalDAV/Schedule/IMip.php | 111 - .../dav/lib/Sabre/CalDAV/Schedule/IOutbox.php | 16 - .../sabre/dav/lib/Sabre/CalDAV/Schedule/Outbox.php | 163 -- .../dav/lib/Sabre/CalDAV/ShareableCalendar.php | 72 - .../sabre/dav/lib/Sabre/CalDAV/SharedCalendar.php | 116 -- .../sabre/dav/lib/Sabre/CalDAV/SharingPlugin.php | 526 ----- .../sabre/dav/lib/Sabre/CalDAV/UserCalendars.php | 342 --- vendor/sabre/dav/lib/Sabre/CalDAV/Version.php | 24 - vendor/sabre/dav/lib/Sabre/CardDAV/AddressBook.php | 315 --- .../lib/Sabre/CardDAV/AddressBookQueryParser.php | 221 -- .../dav/lib/Sabre/CardDAV/AddressBookRoot.php | 80 - .../lib/Sabre/CardDAV/Backend/AbstractBackend.php | 18 - .../lib/Sabre/CardDAV/Backend/BackendInterface.php | 166 -- vendor/sabre/dav/lib/Sabre/CardDAV/Backend/PDO.php | 333 --- vendor/sabre/dav/lib/Sabre/CardDAV/Card.php | 260 --- .../sabre/dav/lib/Sabre/CardDAV/IAddressBook.php | 20 - vendor/sabre/dav/lib/Sabre/CardDAV/ICard.php | 20 - vendor/sabre/dav/lib/Sabre/CardDAV/IDirectory.php | 21 - vendor/sabre/dav/lib/Sabre/CardDAV/Plugin.php | 706 ------- .../CardDAV/Property/SupportedAddressData.php | 72 - .../dav/lib/Sabre/CardDAV/UserAddressBooks.php | 260 --- .../dav/lib/Sabre/CardDAV/VCFExportPlugin.php | 108 - vendor/sabre/dav/lib/Sabre/CardDAV/Version.php | 26 - .../lib/Sabre/DAV/Auth/Backend/AbstractBasic.php | 87 - .../lib/Sabre/DAV/Auth/Backend/AbstractDigest.php | 101 - .../dav/lib/Sabre/DAV/Auth/Backend/Apache.php | 63 - .../Sabre/DAV/Auth/Backend/BackendInterface.php | 36 - .../sabre/dav/lib/Sabre/DAV/Auth/Backend/File.php | 77 - .../sabre/dav/lib/Sabre/DAV/Auth/Backend/PDO.php | 65 - vendor/sabre/dav/lib/Sabre/DAV/Auth/Plugin.php | 112 - .../dav/lib/Sabre/DAV/Browser/GuessContentType.php | 99 - .../dav/lib/Sabre/DAV/Browser/MapGetToPropFind.php | 57 - vendor/sabre/dav/lib/Sabre/DAV/Browser/Plugin.php | 491 ----- .../Sabre/DAV/Browser/assets/icons/addressbook.png | Bin 7232 -> 0 bytes .../Sabre/DAV/Browser/assets/icons/calendar.png | Bin 4388 -> 0 bytes .../lib/Sabre/DAV/Browser/assets/icons/card.png | Bin 5695 -> 0 bytes .../Sabre/DAV/Browser/assets/icons/collection.png | Bin 3474 -> 0 bytes .../lib/Sabre/DAV/Browser/assets/icons/file.png | Bin 2837 -> 0 bytes .../lib/Sabre/DAV/Browser/assets/icons/parent.png | Bin 3474 -> 0 bytes .../Sabre/DAV/Browser/assets/icons/principal.png | Bin 5480 -> 0 bytes vendor/sabre/dav/lib/Sabre/DAV/Client.php | 575 ------ vendor/sabre/dav/lib/Sabre/DAV/Collection.php | 110 - vendor/sabre/dav/lib/Sabre/DAV/Exception.php | 64 - .../dav/lib/Sabre/DAV/Exception/BadRequest.php | 28 - .../sabre/dav/lib/Sabre/DAV/Exception/Conflict.php | 28 - .../lib/Sabre/DAV/Exception/ConflictingLock.php | 37 - .../dav/lib/Sabre/DAV/Exception/FileNotFound.php | 19 - .../dav/lib/Sabre/DAV/Exception/Forbidden.php | 27 - .../Sabre/DAV/Exception/InsufficientStorage.php | 27 - .../Sabre/DAV/Exception/InvalidResourceType.php | 33 - .../dav/lib/Sabre/DAV/Exception/LengthRequired.php | 30 - .../DAV/Exception/LockTokenMatchesRequestUri.php | 41 - .../sabre/dav/lib/Sabre/DAV/Exception/Locked.php | 73 - .../lib/Sabre/DAV/Exception/MethodNotAllowed.php | 45 - .../lib/Sabre/DAV/Exception/NotAuthenticated.php | 30 - .../sabre/dav/lib/Sabre/DAV/Exception/NotFound.php | 28 - .../dav/lib/Sabre/DAV/Exception/NotImplemented.php | 27 - .../lib/Sabre/DAV/Exception/PaymentRequired.php | 30 - .../lib/Sabre/DAV/Exception/PreconditionFailed.php | 71 - .../lib/Sabre/DAV/Exception/ReportNotSupported.php | 32 - .../DAV/Exception/RequestedRangeNotSatisfiable.php | 31 - .../lib/Sabre/DAV/Exception/ServiceUnavailable.php | 30 - .../Sabre/DAV/Exception/UnsupportedMediaType.php | 28 - vendor/sabre/dav/lib/Sabre/DAV/FS/Directory.php | 140 -- vendor/sabre/dav/lib/Sabre/DAV/FS/File.php | 91 - vendor/sabre/dav/lib/Sabre/DAV/FS/Node.php | 82 - vendor/sabre/dav/lib/Sabre/DAV/FSExt/Directory.php | 159 -- vendor/sabre/dav/lib/Sabre/DAV/FSExt/File.php | 146 -- vendor/sabre/dav/lib/Sabre/DAV/FSExt/Node.php | 214 -- vendor/sabre/dav/lib/Sabre/DAV/File.php | 85 - vendor/sabre/dav/lib/Sabre/DAV/ICollection.php | 77 - .../dav/lib/Sabre/DAV/IExtendedCollection.php | 28 - vendor/sabre/dav/lib/Sabre/DAV/IFile.php | 77 - vendor/sabre/dav/lib/Sabre/DAV/INode.php | 46 - vendor/sabre/dav/lib/Sabre/DAV/IProperties.php | 71 - vendor/sabre/dav/lib/Sabre/DAV/IQuota.php | 27 - .../Sabre/DAV/Locks/Backend/AbstractBackend.php | 21 - .../Sabre/DAV/Locks/Backend/BackendInterface.php | 51 - .../sabre/dav/lib/Sabre/DAV/Locks/Backend/FS.php | 193 -- .../sabre/dav/lib/Sabre/DAV/Locks/Backend/File.php | 183 -- .../sabre/dav/lib/Sabre/DAV/Locks/Backend/PDO.php | 167 -- vendor/sabre/dav/lib/Sabre/DAV/Locks/LockInfo.php | 81 - vendor/sabre/dav/lib/Sabre/DAV/Locks/Plugin.php | 649 ------ vendor/sabre/dav/lib/Sabre/DAV/Mount/Plugin.php | 83 - vendor/sabre/dav/lib/Sabre/DAV/Node.php | 55 - vendor/sabre/dav/lib/Sabre/DAV/ObjectTree.php | 159 -- .../dav/lib/Sabre/DAV/PartialUpdate/IFile.php | 39 - .../lib/Sabre/DAV/PartialUpdate/IPatchSupport.php | 48 - .../dav/lib/Sabre/DAV/PartialUpdate/Plugin.php | 246 --- vendor/sabre/dav/lib/Sabre/DAV/Property.php | 31 - .../dav/lib/Sabre/DAV/Property/GetLastModified.php | 78 - vendor/sabre/dav/lib/Sabre/DAV/Property/Href.php | 99 - .../sabre/dav/lib/Sabre/DAV/Property/HrefList.php | 105 - vendor/sabre/dav/lib/Sabre/DAV/Property/IHref.php | 25 - .../dav/lib/Sabre/DAV/Property/LockDiscovery.php | 104 - .../dav/lib/Sabre/DAV/Property/ResourceType.php | 127 -- .../sabre/dav/lib/Sabre/DAV/Property/Response.php | 157 -- .../dav/lib/Sabre/DAV/Property/ResponseList.php | 59 - .../dav/lib/Sabre/DAV/Property/SupportedLock.php | 78 - .../lib/Sabre/DAV/Property/SupportedReportSet.php | 111 - .../sabre/dav/lib/Sabre/DAV/PropertyInterface.php | 21 - vendor/sabre/dav/lib/Sabre/DAV/Server.php | 2178 -------------------- vendor/sabre/dav/lib/Sabre/DAV/ServerPlugin.php | 90 - .../sabre/dav/lib/Sabre/DAV/SimpleCollection.php | 108 - vendor/sabre/dav/lib/Sabre/DAV/SimpleFile.php | 121 -- vendor/sabre/dav/lib/Sabre/DAV/StringUtil.php | 91 - .../lib/Sabre/DAV/TemporaryFileFilterPlugin.php | 289 --- vendor/sabre/dav/lib/Sabre/DAV/Tree.php | 193 -- vendor/sabre/dav/lib/Sabre/DAV/Tree/Filesystem.php | 133 -- vendor/sabre/dav/lib/Sabre/DAV/URLUtil.php | 124 -- vendor/sabre/dav/lib/Sabre/DAV/UUIDUtil.php | 64 - vendor/sabre/dav/lib/Sabre/DAV/Version.php | 24 - vendor/sabre/dav/lib/Sabre/DAV/XMLUtil.php | 191 -- .../Sabre/DAVACL/AbstractPrincipalCollection.php | 155 -- .../dav/lib/Sabre/DAVACL/Exception/AceConflict.php | 35 - .../lib/Sabre/DAVACL/Exception/NeedPrivileges.php | 83 - .../dav/lib/Sabre/DAVACL/Exception/NoAbstract.php | 35 - .../DAVACL/Exception/NotRecognizedPrincipal.php | 35 - .../DAVACL/Exception/NotSupportedPrivilege.php | 35 - vendor/sabre/dav/lib/Sabre/DAVACL/IACL.php | 74 - vendor/sabre/dav/lib/Sabre/DAVACL/IPrincipal.php | 77 - .../dav/lib/Sabre/DAVACL/IPrincipalCollection.php | 42 - vendor/sabre/dav/lib/Sabre/DAVACL/Plugin.php | 1402 ------------- vendor/sabre/dav/lib/Sabre/DAVACL/Principal.php | 281 --- .../DAVACL/PrincipalBackend/AbstractBackend.php | 18 - .../DAVACL/PrincipalBackend/BackendInterface.php | 153 -- .../dav/lib/Sabre/DAVACL/PrincipalBackend/PDO.php | 428 ---- .../dav/lib/Sabre/DAVACL/PrincipalCollection.php | 33 - vendor/sabre/dav/lib/Sabre/DAVACL/Property/Acl.php | 211 -- .../lib/Sabre/DAVACL/Property/AclRestrictions.php | 34 - .../DAVACL/Property/CurrentUserPrivilegeSet.php | 124 -- .../dav/lib/Sabre/DAVACL/Property/Principal.php | 161 -- .../DAVACL/Property/SupportedPrivilegeSet.php | 94 - vendor/sabre/dav/lib/Sabre/DAVACL/Version.php | 24 - vendor/sabre/dav/lib/Sabre/HTTP/AWSAuth.php | 227 -- vendor/sabre/dav/lib/Sabre/HTTP/AbstractAuth.php | 111 - vendor/sabre/dav/lib/Sabre/HTTP/BasicAuth.php | 67 - vendor/sabre/dav/lib/Sabre/HTTP/DigestAuth.php | 240 --- vendor/sabre/dav/lib/Sabre/HTTP/Request.php | 284 --- vendor/sabre/dav/lib/Sabre/HTTP/Response.php | 175 -- vendor/sabre/dav/lib/Sabre/HTTP/Util.php | 82 - vendor/sabre/dav/lib/Sabre/HTTP/Version.php | 24 - vendor/sabre/dav/lib/Sabre/autoload.php | 25 - 401 files changed, 35435 insertions(+), 25998 deletions(-) create mode 100644 vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Backend/PDO.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Backend/SharingSupport.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Backend/SubscriptionSupport.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Backend/SyncSupport.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Calendar.php create mode 100644 vendor/sabre/dav/lib/CalDAV/CalendarHome.php create mode 100644 vendor/sabre/dav/lib/CalDAV/CalendarObject.php create mode 100644 vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php create mode 100644 vendor/sabre/dav/lib/CalDAV/CalendarRoot.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php create mode 100644 vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php create mode 100644 vendor/sabre/dav/lib/CalDAV/ICalendar.php create mode 100644 vendor/sabre/dav/lib/CalDAV/ICalendarObject.php create mode 100644 vendor/sabre/dav/lib/CalDAV/ICalendarObjectContainer.php create mode 100644 vendor/sabre/dav/lib/CalDAV/IShareableCalendar.php create mode 100644 vendor/sabre/dav/lib/CalDAV/ISharedCalendar.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Notifications/Collection.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Notifications/ICollection.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Notifications/INode.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Notifications/Node.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Plugin.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Principal/Collection.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Principal/IProxyRead.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Principal/IProxyWrite.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Principal/ProxyRead.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Principal/User.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Schedule/IMipPlugin.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Schedule/ISchedulingObject.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Schedule/Inbox.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php create mode 100644 vendor/sabre/dav/lib/CalDAV/ShareableCalendar.php create mode 100644 vendor/sabre/dav/lib/CalDAV/SharedCalendar.php create mode 100644 vendor/sabre/dav/lib/CalDAV/SharingPlugin.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Subscriptions/Plugin.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Xml/Notification/SystemStatus.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php create mode 100644 vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php create mode 100644 vendor/sabre/dav/lib/CardDAV/AddressBook.php create mode 100644 vendor/sabre/dav/lib/CardDAV/AddressBookHome.php create mode 100644 vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php create mode 100644 vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php create mode 100644 vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php create mode 100644 vendor/sabre/dav/lib/CardDAV/Backend/PDO.php create mode 100644 vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php create mode 100644 vendor/sabre/dav/lib/CardDAV/Card.php create mode 100644 vendor/sabre/dav/lib/CardDAV/IAddressBook.php create mode 100644 vendor/sabre/dav/lib/CardDAV/ICard.php create mode 100644 vendor/sabre/dav/lib/CardDAV/IDirectory.php create mode 100644 vendor/sabre/dav/lib/CardDAV/Plugin.php create mode 100644 vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php create mode 100644 vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php create mode 100644 vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php create mode 100644 vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php create mode 100644 vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php create mode 100644 vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php create mode 100644 vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php create mode 100644 vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php create mode 100644 vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php create mode 100644 vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBearer.php create mode 100644 vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php create mode 100644 vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php create mode 100644 vendor/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php create mode 100644 vendor/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php create mode 100644 vendor/sabre/dav/lib/DAV/Auth/Backend/File.php create mode 100644 vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php create mode 100644 vendor/sabre/dav/lib/DAV/Auth/Plugin.php create mode 100644 vendor/sabre/dav/lib/DAV/Browser/GuessContentType.php create mode 100644 vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php create mode 100644 vendor/sabre/dav/lib/DAV/Browser/HtmlOutputHelper.php create mode 100644 vendor/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php create mode 100644 vendor/sabre/dav/lib/DAV/Browser/Plugin.php create mode 100644 vendor/sabre/dav/lib/DAV/Browser/PropFindAll.php create mode 100644 vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/ICON-LICENSE create mode 100644 vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.css create mode 100644 vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.eot create mode 100644 vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.otf create mode 100644 vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.svg create mode 100644 vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.ttf create mode 100644 vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.woff create mode 100644 vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css create mode 100644 vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.png create mode 100644 vendor/sabre/dav/lib/DAV/Client.php create mode 100644 vendor/sabre/dav/lib/DAV/Collection.php create mode 100644 vendor/sabre/dav/lib/DAV/CorePlugin.php create mode 100644 vendor/sabre/dav/lib/DAV/Exception.php create mode 100644 vendor/sabre/dav/lib/DAV/Exception/BadRequest.php create mode 100644 vendor/sabre/dav/lib/DAV/Exception/Conflict.php create mode 100644 vendor/sabre/dav/lib/DAV/Exception/ConflictingLock.php create mode 100644 vendor/sabre/dav/lib/DAV/Exception/Forbidden.php create mode 100644 vendor/sabre/dav/lib/DAV/Exception/InsufficientStorage.php create mode 100644 vendor/sabre/dav/lib/DAV/Exception/InvalidResourceType.php create mode 100644 vendor/sabre/dav/lib/DAV/Exception/InvalidSyncToken.php create mode 100644 vendor/sabre/dav/lib/DAV/Exception/LengthRequired.php create mode 100644 vendor/sabre/dav/lib/DAV/Exception/LockTokenMatchesRequestUri.php create mode 100644 vendor/sabre/dav/lib/DAV/Exception/Locked.php create mode 100644 vendor/sabre/dav/lib/DAV/Exception/MethodNotAllowed.php create mode 100644 vendor/sabre/dav/lib/DAV/Exception/NotAuthenticated.php create mode 100644 vendor/sabre/dav/lib/DAV/Exception/NotFound.php create mode 100644 vendor/sabre/dav/lib/DAV/Exception/NotImplemented.php create mode 100644 vendor/sabre/dav/lib/DAV/Exception/PaymentRequired.php create mode 100644 vendor/sabre/dav/lib/DAV/Exception/PreconditionFailed.php create mode 100644 vendor/sabre/dav/lib/DAV/Exception/ReportNotSupported.php create mode 100644 vendor/sabre/dav/lib/DAV/Exception/RequestedRangeNotSatisfiable.php create mode 100644 vendor/sabre/dav/lib/DAV/Exception/ServiceUnavailable.php create mode 100644 vendor/sabre/dav/lib/DAV/Exception/TooManyMatches.php create mode 100644 vendor/sabre/dav/lib/DAV/Exception/UnsupportedMediaType.php create mode 100644 vendor/sabre/dav/lib/DAV/FS/Directory.php create mode 100644 vendor/sabre/dav/lib/DAV/FS/File.php create mode 100644 vendor/sabre/dav/lib/DAV/FS/Node.php create mode 100644 vendor/sabre/dav/lib/DAV/FSExt/Directory.php create mode 100644 vendor/sabre/dav/lib/DAV/FSExt/File.php create mode 100644 vendor/sabre/dav/lib/DAV/File.php create mode 100644 vendor/sabre/dav/lib/DAV/ICollection.php create mode 100644 vendor/sabre/dav/lib/DAV/IExtendedCollection.php create mode 100644 vendor/sabre/dav/lib/DAV/IFile.php create mode 100644 vendor/sabre/dav/lib/DAV/IMoveTarget.php create mode 100644 vendor/sabre/dav/lib/DAV/IMultiGet.php create mode 100644 vendor/sabre/dav/lib/DAV/INode.php create mode 100644 vendor/sabre/dav/lib/DAV/IProperties.php create mode 100644 vendor/sabre/dav/lib/DAV/IQuota.php create mode 100644 vendor/sabre/dav/lib/DAV/Locks/Backend/AbstractBackend.php create mode 100644 vendor/sabre/dav/lib/DAV/Locks/Backend/BackendInterface.php create mode 100644 vendor/sabre/dav/lib/DAV/Locks/Backend/File.php create mode 100644 vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php create mode 100644 vendor/sabre/dav/lib/DAV/Locks/LockInfo.php create mode 100644 vendor/sabre/dav/lib/DAV/Locks/Plugin.php create mode 100644 vendor/sabre/dav/lib/DAV/MkCol.php create mode 100644 vendor/sabre/dav/lib/DAV/Mount/Plugin.php create mode 100644 vendor/sabre/dav/lib/DAV/Node.php create mode 100644 vendor/sabre/dav/lib/DAV/PartialUpdate/IPatchSupport.php create mode 100644 vendor/sabre/dav/lib/DAV/PartialUpdate/Plugin.php create mode 100644 vendor/sabre/dav/lib/DAV/PropFind.php create mode 100644 vendor/sabre/dav/lib/DAV/PropPatch.php create mode 100644 vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/BackendInterface.php create mode 100644 vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php create mode 100644 vendor/sabre/dav/lib/DAV/PropertyStorage/Plugin.php create mode 100644 vendor/sabre/dav/lib/DAV/Server.php create mode 100644 vendor/sabre/dav/lib/DAV/ServerPlugin.php create mode 100644 vendor/sabre/dav/lib/DAV/SimpleCollection.php create mode 100644 vendor/sabre/dav/lib/DAV/SimpleFile.php create mode 100644 vendor/sabre/dav/lib/DAV/StringUtil.php create mode 100644 vendor/sabre/dav/lib/DAV/Sync/ISyncCollection.php create mode 100644 vendor/sabre/dav/lib/DAV/Sync/Plugin.php create mode 100644 vendor/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php create mode 100644 vendor/sabre/dav/lib/DAV/Tree.php create mode 100644 vendor/sabre/dav/lib/DAV/UUIDUtil.php create mode 100644 vendor/sabre/dav/lib/DAV/Version.php create mode 100644 vendor/sabre/dav/lib/DAV/Xml/Element/Prop.php create mode 100644 vendor/sabre/dav/lib/DAV/Xml/Element/Response.php create mode 100644 vendor/sabre/dav/lib/DAV/Xml/Property/Complex.php create mode 100644 vendor/sabre/dav/lib/DAV/Xml/Property/GetLastModified.php create mode 100644 vendor/sabre/dav/lib/DAV/Xml/Property/Href.php create mode 100644 vendor/sabre/dav/lib/DAV/Xml/Property/LockDiscovery.php create mode 100644 vendor/sabre/dav/lib/DAV/Xml/Property/ResourceType.php create mode 100644 vendor/sabre/dav/lib/DAV/Xml/Property/SupportedLock.php create mode 100644 vendor/sabre/dav/lib/DAV/Xml/Property/SupportedMethodSet.php create mode 100644 vendor/sabre/dav/lib/DAV/Xml/Property/SupportedReportSet.php create mode 100644 vendor/sabre/dav/lib/DAV/Xml/Request/Lock.php create mode 100644 vendor/sabre/dav/lib/DAV/Xml/Request/MkCol.php create mode 100644 vendor/sabre/dav/lib/DAV/Xml/Request/PropFind.php create mode 100644 vendor/sabre/dav/lib/DAV/Xml/Request/PropPatch.php create mode 100644 vendor/sabre/dav/lib/DAV/Xml/Request/SyncCollectionReport.php create mode 100644 vendor/sabre/dav/lib/DAV/Xml/Response/MultiStatus.php create mode 100644 vendor/sabre/dav/lib/DAV/Xml/Service.php create mode 100644 vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php create mode 100644 vendor/sabre/dav/lib/DAVACL/Exception/AceConflict.php create mode 100644 vendor/sabre/dav/lib/DAVACL/Exception/NeedPrivileges.php create mode 100644 vendor/sabre/dav/lib/DAVACL/Exception/NoAbstract.php create mode 100644 vendor/sabre/dav/lib/DAVACL/Exception/NotRecognizedPrincipal.php create mode 100644 vendor/sabre/dav/lib/DAVACL/Exception/NotSupportedPrivilege.php create mode 100644 vendor/sabre/dav/lib/DAVACL/FS/Collection.php create mode 100644 vendor/sabre/dav/lib/DAVACL/FS/File.php create mode 100644 vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php create mode 100644 vendor/sabre/dav/lib/DAVACL/IACL.php create mode 100644 vendor/sabre/dav/lib/DAVACL/IPrincipal.php create mode 100644 vendor/sabre/dav/lib/DAVACL/IPrincipalCollection.php create mode 100644 vendor/sabre/dav/lib/DAVACL/Plugin.php create mode 100644 vendor/sabre/dav/lib/DAVACL/Principal.php create mode 100644 vendor/sabre/dav/lib/DAVACL/PrincipalBackend/AbstractBackend.php create mode 100644 vendor/sabre/dav/lib/DAVACL/PrincipalBackend/BackendInterface.php create mode 100644 vendor/sabre/dav/lib/DAVACL/PrincipalBackend/CreatePrincipalSupport.php create mode 100644 vendor/sabre/dav/lib/DAVACL/PrincipalBackend/PDO.php create mode 100644 vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php create mode 100644 vendor/sabre/dav/lib/DAVACL/Xml/Property/Acl.php create mode 100644 vendor/sabre/dav/lib/DAVACL/Xml/Property/AclRestrictions.php create mode 100644 vendor/sabre/dav/lib/DAVACL/Xml/Property/CurrentUserPrivilegeSet.php create mode 100644 vendor/sabre/dav/lib/DAVACL/Xml/Property/Principal.php create mode 100644 vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php create mode 100644 vendor/sabre/dav/lib/DAVACL/Xml/Request/ExpandPropertyReport.php create mode 100644 vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalPropertySearchReport.php create mode 100644 vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalSearchPropertySetReport.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Backend/AbstractBackend.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Backend/BackendInterface.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Backend/NotificationSupport.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Backend/PDO.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Backend/SharingSupport.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Calendar.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/CalendarObject.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/CalendarQueryParser.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/CalendarQueryValidator.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/CalendarRootNode.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Exception/InvalidComponentType.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/ICSExportPlugin.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/ICalendar.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/ICalendarObject.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/IShareableCalendar.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/ISharedCalendar.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Collection.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/ICollection.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/INode.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/INotificationType.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Node.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/Invite.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/InviteReply.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/SystemStatus.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Plugin.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Principal/Collection.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Principal/IProxyRead.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Principal/IProxyWrite.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Principal/ProxyRead.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Principal/ProxyWrite.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Principal/User.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Property/AllowedSharingModes.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Property/Invite.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Property/ScheduleCalendarTransp.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCalendarComponentSet.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCalendarData.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCollationSet.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Schedule/IMip.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Schedule/IOutbox.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Schedule/Outbox.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/ShareableCalendar.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/SharedCalendar.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/SharingPlugin.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/UserCalendars.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CalDAV/Version.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CardDAV/AddressBook.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CardDAV/AddressBookQueryParser.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CardDAV/AddressBookRoot.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CardDAV/Backend/AbstractBackend.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CardDAV/Backend/BackendInterface.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CardDAV/Backend/PDO.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CardDAV/Card.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CardDAV/IAddressBook.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CardDAV/ICard.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CardDAV/IDirectory.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CardDAV/Plugin.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CardDAV/Property/SupportedAddressData.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CardDAV/UserAddressBooks.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CardDAV/VCFExportPlugin.php delete mode 100644 vendor/sabre/dav/lib/Sabre/CardDAV/Version.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/AbstractBasic.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/AbstractDigest.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/Apache.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/BackendInterface.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/File.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/PDO.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Auth/Plugin.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Browser/GuessContentType.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Browser/MapGetToPropFind.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Browser/Plugin.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/addressbook.png delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/calendar.png delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/card.png delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/collection.png delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/file.png delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/parent.png delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/principal.png delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Client.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Collection.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Exception.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Exception/BadRequest.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Exception/Conflict.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Exception/ConflictingLock.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Exception/FileNotFound.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Exception/Forbidden.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Exception/InsufficientStorage.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Exception/InvalidResourceType.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Exception/LengthRequired.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Exception/LockTokenMatchesRequestUri.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Exception/Locked.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Exception/MethodNotAllowed.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Exception/NotAuthenticated.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Exception/NotFound.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Exception/NotImplemented.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Exception/PaymentRequired.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Exception/PreconditionFailed.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Exception/ReportNotSupported.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Exception/RequestedRangeNotSatisfiable.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Exception/ServiceUnavailable.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Exception/UnsupportedMediaType.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/FS/Directory.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/FS/File.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/FS/Node.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/FSExt/Directory.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/FSExt/File.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/FSExt/Node.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/File.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/ICollection.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/IExtendedCollection.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/IFile.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/INode.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/IProperties.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/IQuota.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/AbstractBackend.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/BackendInterface.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/FS.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/File.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/PDO.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Locks/LockInfo.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Locks/Plugin.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Mount/Plugin.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Node.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/ObjectTree.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/PartialUpdate/IFile.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/PartialUpdate/IPatchSupport.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/PartialUpdate/Plugin.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Property.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Property/GetLastModified.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Property/Href.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Property/HrefList.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Property/IHref.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Property/LockDiscovery.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Property/ResourceType.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Property/Response.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Property/ResponseList.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Property/SupportedLock.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Property/SupportedReportSet.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/PropertyInterface.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Server.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/ServerPlugin.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/SimpleCollection.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/SimpleFile.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/StringUtil.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/TemporaryFileFilterPlugin.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Tree.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Tree/Filesystem.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/URLUtil.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/UUIDUtil.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/Version.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAV/XMLUtil.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAVACL/AbstractPrincipalCollection.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAVACL/Exception/AceConflict.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAVACL/Exception/NeedPrivileges.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAVACL/Exception/NoAbstract.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAVACL/Exception/NotRecognizedPrincipal.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAVACL/Exception/NotSupportedPrivilege.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAVACL/IACL.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAVACL/IPrincipal.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAVACL/IPrincipalCollection.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAVACL/Plugin.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAVACL/Principal.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAVACL/PrincipalBackend/AbstractBackend.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAVACL/PrincipalBackend/BackendInterface.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAVACL/PrincipalBackend/PDO.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAVACL/PrincipalCollection.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAVACL/Property/Acl.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAVACL/Property/AclRestrictions.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAVACL/Property/CurrentUserPrivilegeSet.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAVACL/Property/Principal.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAVACL/Property/SupportedPrivilegeSet.php delete mode 100644 vendor/sabre/dav/lib/Sabre/DAVACL/Version.php delete mode 100644 vendor/sabre/dav/lib/Sabre/HTTP/AWSAuth.php delete mode 100644 vendor/sabre/dav/lib/Sabre/HTTP/AbstractAuth.php delete mode 100644 vendor/sabre/dav/lib/Sabre/HTTP/BasicAuth.php delete mode 100644 vendor/sabre/dav/lib/Sabre/HTTP/DigestAuth.php delete mode 100644 vendor/sabre/dav/lib/Sabre/HTTP/Request.php delete mode 100644 vendor/sabre/dav/lib/Sabre/HTTP/Response.php delete mode 100644 vendor/sabre/dav/lib/Sabre/HTTP/Util.php delete mode 100644 vendor/sabre/dav/lib/Sabre/HTTP/Version.php delete mode 100644 vendor/sabre/dav/lib/Sabre/autoload.php (limited to 'vendor/sabre/dav/lib') diff --git a/vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php b/vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php new file mode 100644 index 000000000..d58b4a46e --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php @@ -0,0 +1,226 @@ +getCalendarObject($calendarId, $uri); + }, $uris); + + } + + /** + * Performs a calendar-query on the contents of this calendar. + * + * The calendar-query is defined in RFC4791 : CalDAV. Using the + * calendar-query it is possible for a client to request a specific set of + * object, based on contents of iCalendar properties, date-ranges and + * iCalendar component types (VTODO, VEVENT). + * + * This method should just return a list of (relative) urls that match this + * query. + * + * The list of filters are specified as an array. The exact array is + * documented by \Sabre\CalDAV\CalendarQueryParser. + * + * Note that it is extremely likely that getCalendarObject for every path + * returned from this method will be called almost immediately after. You + * may want to anticipate this to speed up these requests. + * + * This method provides a default implementation, which parses *all* the + * iCalendar objects in the specified calendar. + * + * This default may well be good enough for personal use, and calendars + * that aren't very large. But if you anticipate high usage, big calendars + * or high loads, you are strongly adviced to optimize certain paths. + * + * The best way to do so is override this method and to optimize + * specifically for 'common filters'. + * + * Requests that are extremely common are: + * * requests for just VEVENTS + * * requests for just VTODO + * * requests with a time-range-filter on either VEVENT or VTODO. + * + * ..and combinations of these requests. It may not be worth it to try to + * handle every possible situation and just rely on the (relatively + * easy to use) CalendarQueryValidator to handle the rest. + * + * Note that especially time-range-filters may be difficult to parse. A + * time-range filter specified on a VEVENT must for instance also handle + * recurrence rules correctly. + * A good example of how to interprete all these filters can also simply + * be found in \Sabre\CalDAV\CalendarQueryFilter. This class is as correct + * as possible, so it gives you a good idea on what type of stuff you need + * to think of. + * + * @param mixed $calendarId + * @param array $filters + * @return array + */ + function calendarQuery($calendarId, array $filters) { + + $result = []; + $objects = $this->getCalendarObjects($calendarId); + + foreach ($objects as $object) { + + if ($this->validateFilterForObject($object, $filters)) { + $result[] = $object['uri']; + } + + } + + return $result; + + } + + /** + * This method validates if a filter (as passed to calendarQuery) matches + * the given object. + * + * @param array $object + * @param array $filters + * @return bool + */ + protected function validateFilterForObject(array $object, array $filters) { + + // Unfortunately, setting the 'calendardata' here is optional. If + // it was excluded, we actually need another call to get this as + // well. + if (!isset($object['calendardata'])) { + $object = $this->getCalendarObject($object['calendarid'], $object['uri']); + } + + $vObject = VObject\Reader::read($object['calendardata']); + + $validator = new CalDAV\CalendarQueryValidator(); + $result = $validator->validate($vObject, $filters); + + // Destroy circular references so PHP will GC the object. + $vObject->destroy(); + + return $result; + + } + + /** + * Searches through all of a users calendars and calendar objects to find + * an object with a specific UID. + * + * This method should return the path to this object, relative to the + * calendar home, so this path usually only contains two parts: + * + * calendarpath/objectpath.ics + * + * If the uid is not found, return null. + * + * This method should only consider * objects that the principal owns, so + * any calendars owned by other principals that also appear in this + * collection should be ignored. + * + * @param string $principalUri + * @param string $uid + * @return string|null + */ + function getCalendarObjectByUID($principalUri, $uid) { + + // Note: this is a super slow naive implementation of this method. You + // are highly recommended to optimize it, if your backend allows it. + foreach ($this->getCalendarsForUser($principalUri) as $calendar) { + + // We must ignore calendars owned by other principals. + if ($calendar['principaluri'] !== $principalUri) { + continue; + } + + // Ignore calendars that are shared. + if (isset($calendar['{http://sabredav.org/ns}owner-principal']) && $calendar['{http://sabredav.org/ns}owner-principal'] !== $principalUri) { + continue; + } + + $results = $this->calendarQuery( + $calendar['id'], + [ + 'name' => 'VCALENDAR', + 'prop-filters' => [], + 'comp-filters' => [ + [ + 'name' => 'VEVENT', + 'is-not-defined' => false, + 'time-range' => null, + 'comp-filters' => [], + 'prop-filters' => [ + [ + 'name' => 'UID', + 'is-not-defined' => false, + 'time-range' => null, + 'text-match' => [ + 'value' => $uid, + 'negate-condition' => false, + 'collation' => 'i;octet', + ], + 'param-filters' => [], + ], + ] + ] + ], + ] + ); + if ($results) { + // We have a match + return $calendar['uri'] . '/' . $results[0]; + } + + } + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php b/vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php new file mode 100644 index 000000000..7513fb60d --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php @@ -0,0 +1,268 @@ + 'displayname', + '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description', + '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone', + '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder', + '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor', + ]; + + /** + * List of subscription properties, and how they map to database fieldnames. + * + * @var array + */ + public $subscriptionPropertyMap = [ + '{DAV:}displayname' => 'displayname', + '{http://apple.com/ns/ical/}refreshrate' => 'refreshrate', + '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder', + '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor', + '{http://calendarserver.org/ns/}subscribed-strip-todos' => 'striptodos', + '{http://calendarserver.org/ns/}subscribed-strip-alarms' => 'stripalarms', + '{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments', + ]; + + /** + * Creates the backend + * + * @param \PDO $pdo + */ + function __construct(\PDO $pdo) { + + $this->pdo = $pdo; + + } + + /** + * Returns a list of calendars for a principal. + * + * Every project is an array with the following keys: + * * id, a unique id that will be used by other functions to modify the + * calendar. This can be the same as the uri or a database key. + * * uri. This is just the 'base uri' or 'filename' of the calendar. + * * principaluri. The owner of the calendar. Almost always the same as + * principalUri passed to this method. + * + * Furthermore it can contain webdav properties in clark notation. A very + * common one is '{DAV:}displayname'. + * + * Many clients also require: + * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set + * For this property, you can just return an instance of + * Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet. + * + * If you return {http://sabredav.org/ns}read-only and set the value to 1, + * ACL will automatically be put in read-only mode. + * + * @param string $principalUri + * @return array + */ + function getCalendarsForUser($principalUri) { + + $fields = array_values($this->propertyMap); + $fields[] = 'id'; + $fields[] = 'uri'; + $fields[] = 'synctoken'; + $fields[] = 'components'; + $fields[] = 'principaluri'; + $fields[] = 'transparent'; + + // Making fields a comma-delimited list + $fields = implode(', ', $fields); + $stmt = $this->pdo->prepare("SELECT " . $fields . " FROM " . $this->calendarTableName . " WHERE principaluri = ? ORDER BY calendarorder ASC"); + $stmt->execute([$principalUri]); + + $calendars = []; + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + + $components = []; + if ($row['components']) { + $components = explode(',', $row['components']); + } + + $calendar = [ + 'id' => $row['id'], + 'uri' => $row['uri'], + 'principaluri' => $row['principaluri'], + '{' . CalDAV\Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken'] ? $row['synctoken'] : '0'), + '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0', + '{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet($components), + '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' => new CalDAV\Xml\Property\ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'), + ]; + + + foreach ($this->propertyMap as $xmlName => $dbName) { + $calendar[$xmlName] = $row[$dbName]; + } + + $calendars[] = $calendar; + + } + + return $calendars; + + } + + /** + * Creates a new calendar for a principal. + * + * If the creation was a success, an id must be returned that can be used + * to reference this calendar in other methods, such as updateCalendar. + * + * @param string $principalUri + * @param string $calendarUri + * @param array $properties + * @return string + */ + function createCalendar($principalUri, $calendarUri, array $properties) { + + $fieldNames = [ + 'principaluri', + 'uri', + 'synctoken', + 'transparent', + ]; + $values = [ + ':principaluri' => $principalUri, + ':uri' => $calendarUri, + ':synctoken' => 1, + ':transparent' => 0, + ]; + + // Default value + $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'; + $fieldNames[] = 'components'; + if (!isset($properties[$sccs])) { + $values[':components'] = 'VEVENT,VTODO'; + } else { + if (!($properties[$sccs] instanceof CalDAV\Xml\Property\SupportedCalendarComponentSet)) { + throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet'); + } + $values[':components'] = implode(',', $properties[$sccs]->getValue()); + } + $transp = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp'; + if (isset($properties[$transp])) { + $values[':transparent'] = $properties[$transp]->getValue() === 'transparent'; + } + + foreach ($this->propertyMap as $xmlName => $dbName) { + if (isset($properties[$xmlName])) { + + $values[':' . $dbName] = $properties[$xmlName]; + $fieldNames[] = $dbName; + } + } + + $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")"); + $stmt->execute($values); + + return $this->pdo->lastInsertId(); + + } + + /** + * Updates properties for a calendar. + * + * The list of mutations is stored in a Sabre\DAV\PropPatch object. + * To do the actual updates, you must tell this object which properties + * you're going to process with the handle() method. + * + * Calling the handle method is like telling the PropPatch object "I + * promise I can handle updating this property". + * + * Read the PropPatch documenation for more info and examples. + * + * @param string $calendarId + * @param \Sabre\DAV\PropPatch $propPatch + * @return void + */ + function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch) { + + $supportedProperties = array_keys($this->propertyMap); + $supportedProperties[] = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp'; + + $propPatch->handle($supportedProperties, function($mutations) use ($calendarId) { + $newValues = []; + foreach ($mutations as $propertyName => $propertyValue) { + + switch ($propertyName) { + case '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' : + $fieldName = 'transparent'; + $newValues[$fieldName] = $propertyValue->getValue() === 'transparent'; + break; + default : + $fieldName = $this->propertyMap[$propertyName]; + $newValues[$fieldName] = $propertyValue; + break; + } + + } + $valuesSql = []; + foreach ($newValues as $fieldName => $value) { + $valuesSql[] = $fieldName . ' = ?'; + } + + $stmt = $this->pdo->prepare("UPDATE " . $this->calendarTableName . " SET " . implode(', ', $valuesSql) . " WHERE id = ?"); + $newValues['id'] = $calendarId; + $stmt->execute(array_values($newValues)); + + $this->addChange($calendarId, "", 2); + + return true; + + }); + + } + + /** + * Delete a calendar and all it's objects + * + * @param string $calendarId + * @return void + */ + function deleteCalendar($calendarId) { + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ?'); + $stmt->execute([$calendarId]); + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarTableName . ' WHERE id = ?'); + $stmt->execute([$calendarId]); + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarChangesTableName . ' WHERE calendarid = ?'); + $stmt->execute([$calendarId]); + + } + + /** + * Returns all calendar objects within a calendar. + * + * Every item contains an array with the following keys: + * * calendardata - The iCalendar-compatible calendar data + * * uri - a unique key which will be used to construct the uri. This can + * be any arbitrary string, but making sure it ends with '.ics' is a + * good idea. This is only the basename, or filename, not the full + * path. + * * lastmodified - a timestamp of the last modification time + * * etag - An arbitrary string, surrounded by double-quotes. (e.g.: + * ' "abcdef"') + * * size - The size of the calendar objects, in bytes. + * * component - optional, a string containing the type of object, such + * as 'vevent' or 'vtodo'. If specified, this will be used to populate + * the Content-Type header. + * + * Note that the etag is optional, but it's highly encouraged to return for + * speed reasons. + * + * The calendardata is also optional. If it's not returned + * 'getCalendarObject' will be called later, which *is* expected to return + * calendardata. + * + * If neither etag or size are specified, the calendardata will be + * used/fetched to determine these numbers. If both are specified the + * amount of times this is needed is reduced by a great degree. + * + * @param string $calendarId + * @return array + */ + function getCalendarObjects($calendarId) { + + $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ?'); + $stmt->execute([$calendarId]); + + $result = []; + foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) { + $result[] = [ + 'id' => $row['id'], + 'uri' => $row['uri'], + 'lastmodified' => $row['lastmodified'], + 'etag' => '"' . $row['etag'] . '"', + 'calendarid' => $row['calendarid'], + 'size' => (int)$row['size'], + 'component' => strtolower($row['componenttype']), + ]; + } + + return $result; + + } + + /** + * Returns information from a single calendar object, based on it's object + * uri. + * + * The object uri is only the basename, or filename and not a full path. + * + * The returned array must have the same keys as getCalendarObjects. The + * 'calendardata' object is required here though, while it's not required + * for getCalendarObjects. + * + * This method must return null if the object did not exist. + * + * @param string $calendarId + * @param string $objectUri + * @return array|null + */ + function getCalendarObject($calendarId, $objectUri) { + + $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri = ?'); + $stmt->execute([$calendarId, $objectUri]); + $row = $stmt->fetch(\PDO::FETCH_ASSOC); + + if (!$row) return null; + + return [ + 'id' => $row['id'], + 'uri' => $row['uri'], + 'lastmodified' => $row['lastmodified'], + 'etag' => '"' . $row['etag'] . '"', + 'calendarid' => $row['calendarid'], + 'size' => (int)$row['size'], + 'calendardata' => $row['calendardata'], + 'component' => strtolower($row['componenttype']), + ]; + + } + + /** + * Returns a list of calendar objects. + * + * This method should work identical to getCalendarObject, but instead + * return all the calendar objects in the list as an array. + * + * If the backend supports this, it may allow for some speed-ups. + * + * @param mixed $calendarId + * @param array $uris + * @return array + */ + function getMultipleCalendarObjects($calendarId, array $uris) { + + $query = 'SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri IN ('; + // Inserting a whole bunch of question marks + $query .= implode(',', array_fill(0, count($uris), '?')); + $query .= ')'; + + $stmt = $this->pdo->prepare($query); + $stmt->execute(array_merge([$calendarId], $uris)); + + $result = []; + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + + $result[] = [ + 'id' => $row['id'], + 'uri' => $row['uri'], + 'lastmodified' => $row['lastmodified'], + 'etag' => '"' . $row['etag'] . '"', + 'calendarid' => $row['calendarid'], + 'size' => (int)$row['size'], + 'calendardata' => $row['calendardata'], + 'component' => strtolower($row['componenttype']), + ]; + + } + return $result; + + } + + + /** + * Creates a new calendar object. + * + * The object uri is only the basename, or filename and not a full path. + * + * It is possible return an etag from this function, which will be used in + * the response to this PUT request. Note that the ETag must be surrounded + * by double-quotes. + * + * However, you should only really return this ETag if you don't mangle the + * calendar-data. If the result of a subsequent GET to this object is not + * the exact same as this request body, you should omit the ETag. + * + * @param mixed $calendarId + * @param string $objectUri + * @param string $calendarData + * @return string|null + */ + function createCalendarObject($calendarId, $objectUri, $calendarData) { + + $extraData = $this->getDenormalizedData($calendarData); + + $stmt = $this->pdo->prepare('INSERT INTO ' . $this->calendarObjectTableName . ' (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES (?,?,?,?,?,?,?,?,?,?)'); + $stmt->execute([ + $calendarId, + $objectUri, + $calendarData, + time(), + $extraData['etag'], + $extraData['size'], + $extraData['componentType'], + $extraData['firstOccurence'], + $extraData['lastOccurence'], + $extraData['uid'], + ]); + $this->addChange($calendarId, $objectUri, 1); + + return '"' . $extraData['etag'] . '"'; + + } + + /** + * Updates an existing calendarobject, based on it's uri. + * + * The object uri is only the basename, or filename and not a full path. + * + * It is possible return an etag from this function, which will be used in + * the response to this PUT request. Note that the ETag must be surrounded + * by double-quotes. + * + * However, you should only really return this ETag if you don't mangle the + * calendar-data. If the result of a subsequent GET to this object is not + * the exact same as this request body, you should omit the ETag. + * + * @param mixed $calendarId + * @param string $objectUri + * @param string $calendarData + * @return string|null + */ + function updateCalendarObject($calendarId, $objectUri, $calendarData) { + + $extraData = $this->getDenormalizedData($calendarData); + + $stmt = $this->pdo->prepare('UPDATE ' . $this->calendarObjectTableName . ' SET calendardata = ?, lastmodified = ?, etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ?, uid = ? WHERE calendarid = ? AND uri = ?'); + $stmt->execute([$calendarData, time(), $extraData['etag'], $extraData['size'], $extraData['componentType'], $extraData['firstOccurence'], $extraData['lastOccurence'], $extraData['uid'], $calendarId, $objectUri]); + + $this->addChange($calendarId, $objectUri, 2); + + return '"' . $extraData['etag'] . '"'; + + } + + /** + * Parses some information from calendar objects, used for optimized + * calendar-queries. + * + * Returns an array with the following keys: + * * etag - An md5 checksum of the object without the quotes. + * * size - Size of the object in bytes + * * componentType - VEVENT, VTODO or VJOURNAL + * * firstOccurence + * * lastOccurence + * * uid - value of the UID property + * + * @param string $calendarData + * @return array + */ + protected function getDenormalizedData($calendarData) { + + $vObject = VObject\Reader::read($calendarData); + $componentType = null; + $component = null; + $firstOccurence = null; + $lastOccurence = null; + $uid = null; + foreach ($vObject->getComponents() as $component) { + if ($component->name !== 'VTIMEZONE') { + $componentType = $component->name; + $uid = (string)$component->UID; + break; + } + } + if (!$componentType) { + throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component'); + } + if ($componentType === 'VEVENT') { + $firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp(); + // Finding the last occurence is a bit harder + if (!isset($component->RRULE)) { + if (isset($component->DTEND)) { + $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp(); + } elseif (isset($component->DURATION)) { + $endDate = clone $component->DTSTART->getDateTime(); + $endDate = $endDate->add(VObject\DateTimeParser::parse($component->DURATION->getValue())); + $lastOccurence = $endDate->getTimeStamp(); + } elseif (!$component->DTSTART->hasTime()) { + $endDate = clone $component->DTSTART->getDateTime(); + $endDate = $endDate->modify('+1 day'); + $lastOccurence = $endDate->getTimeStamp(); + } else { + $lastOccurence = $firstOccurence; + } + } else { + $it = new VObject\Recur\EventIterator($vObject, (string)$component->UID); + $maxDate = new \DateTime(self::MAX_DATE); + if ($it->isInfinite()) { + $lastOccurence = $maxDate->getTimeStamp(); + } else { + $end = $it->getDtEnd(); + while ($it->valid() && $end < $maxDate) { + $end = $it->getDtEnd(); + $it->next(); + + } + $lastOccurence = $end->getTimeStamp(); + } + + } + } + + // Destroy circular references to PHP will GC the object. + $vObject->destroy(); + + return [ + 'etag' => md5($calendarData), + 'size' => strlen($calendarData), + 'componentType' => $componentType, + 'firstOccurence' => $firstOccurence, + 'lastOccurence' => $lastOccurence, + 'uid' => $uid, + ]; + + } + + /** + * Deletes an existing calendar object. + * + * The object uri is only the basename, or filename and not a full path. + * + * @param string $calendarId + * @param string $objectUri + * @return void + */ + function deleteCalendarObject($calendarId, $objectUri) { + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri = ?'); + $stmt->execute([$calendarId, $objectUri]); + + $this->addChange($calendarId, $objectUri, 3); + + } + + /** + * Performs a calendar-query on the contents of this calendar. + * + * The calendar-query is defined in RFC4791 : CalDAV. Using the + * calendar-query it is possible for a client to request a specific set of + * object, based on contents of iCalendar properties, date-ranges and + * iCalendar component types (VTODO, VEVENT). + * + * This method should just return a list of (relative) urls that match this + * query. + * + * The list of filters are specified as an array. The exact array is + * documented by \Sabre\CalDAV\CalendarQueryParser. + * + * Note that it is extremely likely that getCalendarObject for every path + * returned from this method will be called almost immediately after. You + * may want to anticipate this to speed up these requests. + * + * This method provides a default implementation, which parses *all* the + * iCalendar objects in the specified calendar. + * + * This default may well be good enough for personal use, and calendars + * that aren't very large. But if you anticipate high usage, big calendars + * or high loads, you are strongly adviced to optimize certain paths. + * + * The best way to do so is override this method and to optimize + * specifically for 'common filters'. + * + * Requests that are extremely common are: + * * requests for just VEVENTS + * * requests for just VTODO + * * requests with a time-range-filter on a VEVENT. + * + * ..and combinations of these requests. It may not be worth it to try to + * handle every possible situation and just rely on the (relatively + * easy to use) CalendarQueryValidator to handle the rest. + * + * Note that especially time-range-filters may be difficult to parse. A + * time-range filter specified on a VEVENT must for instance also handle + * recurrence rules correctly. + * A good example of how to interprete all these filters can also simply + * be found in \Sabre\CalDAV\CalendarQueryFilter. This class is as correct + * as possible, so it gives you a good idea on what type of stuff you need + * to think of. + * + * This specific implementation (for the PDO) backend optimizes filters on + * specific components, and VEVENT time-ranges. + * + * @param string $calendarId + * @param array $filters + * @return array + */ + function calendarQuery($calendarId, array $filters) { + + $componentType = null; + $requirePostFilter = true; + $timeRange = null; + + // if no filters were specified, we don't need to filter after a query + if (!$filters['prop-filters'] && !$filters['comp-filters']) { + $requirePostFilter = false; + } + + // Figuring out if there's a component filter + if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) { + $componentType = $filters['comp-filters'][0]['name']; + + // Checking if we need post-filters + if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) { + $requirePostFilter = false; + } + // There was a time-range filter + if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range'])) { + $timeRange = $filters['comp-filters'][0]['time-range']; + + // If start time OR the end time is not specified, we can do a + // 100% accurate mysql query. + if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) { + $requirePostFilter = false; + } + } + + } + + if ($requirePostFilter) { + $query = "SELECT uri, calendardata FROM " . $this->calendarObjectTableName . " WHERE calendarid = :calendarid"; + } else { + $query = "SELECT uri FROM " . $this->calendarObjectTableName . " WHERE calendarid = :calendarid"; + } + + $values = [ + 'calendarid' => $calendarId, + ]; + + if ($componentType) { + $query .= " AND componenttype = :componenttype"; + $values['componenttype'] = $componentType; + } + + if ($timeRange && $timeRange['start']) { + $query .= " AND lastoccurence > :startdate"; + $values['startdate'] = $timeRange['start']->getTimeStamp(); + } + if ($timeRange && $timeRange['end']) { + $query .= " AND firstoccurence < :enddate"; + $values['enddate'] = $timeRange['end']->getTimeStamp(); + } + + $stmt = $this->pdo->prepare($query); + $stmt->execute($values); + + $result = []; + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + if ($requirePostFilter) { + if (!$this->validateFilterForObject($row, $filters)) { + continue; + } + } + $result[] = $row['uri']; + + } + + return $result; + + } + + /** + * Searches through all of a users calendars and calendar objects to find + * an object with a specific UID. + * + * This method should return the path to this object, relative to the + * calendar home, so this path usually only contains two parts: + * + * calendarpath/objectpath.ics + * + * If the uid is not found, return null. + * + * This method should only consider * objects that the principal owns, so + * any calendars owned by other principals that also appear in this + * collection should be ignored. + * + * @param string $principalUri + * @param string $uid + * @return string|null + */ + function getCalendarObjectByUID($principalUri, $uid) { + + $query = <<calendarObjectTableName AS calendarobjects +LEFT JOIN + $this->calendarTableName AS calendars + ON calendarobjects.calendarid = calendars.id +WHERE + calendars.principaluri = ? + AND + calendarobjects.uid = ? +SQL; + + $stmt = $this->pdo->prepare($query); + $stmt->execute([$principalUri, $uid]); + + if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + return $row['calendaruri'] . '/' . $row['objecturi']; + } + + } + + /** + * The getChanges method returns all the changes that have happened, since + * the specified syncToken in the specified calendar. + * + * This function should return an array, such as the following: + * + * [ + * 'syncToken' => 'The current synctoken', + * 'added' => [ + * 'new.txt', + * ], + * 'modified' => [ + * 'modified.txt', + * ], + * 'deleted' => [ + * 'foo.php.bak', + * 'old.txt' + * ] + * ]; + * + * The returned syncToken property should reflect the *current* syncToken + * of the calendar, as reported in the {http://sabredav.org/ns}sync-token + * property this is needed here too, to ensure the operation is atomic. + * + * If the $syncToken argument is specified as null, this is an initial + * sync, and all members should be reported. + * + * The modified property is an array of nodenames that have changed since + * the last token. + * + * The deleted property is an array with nodenames, that have been deleted + * from collection. + * + * The $syncLevel argument is basically the 'depth' of the report. If it's + * 1, you only have to report changes that happened only directly in + * immediate descendants. If it's 2, it should also include changes from + * the nodes below the child collections. (grandchildren) + * + * The $limit argument allows a client to specify how many results should + * be returned at most. If the limit is not specified, it should be treated + * as infinite. + * + * If the limit (infinite or not) is higher than you're willing to return, + * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. + * + * If the syncToken is expired (due to data cleanup) or unknown, you must + * return null. + * + * The limit is 'suggestive'. You are free to ignore it. + * + * @param string $calendarId + * @param string $syncToken + * @param int $syncLevel + * @param int $limit + * @return array + */ + function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null) { + + // Current synctoken + $stmt = $this->pdo->prepare('SELECT synctoken FROM ' . $this->calendarTableName . ' WHERE id = ?'); + $stmt->execute([ $calendarId ]); + $currentToken = $stmt->fetchColumn(0); + + if (is_null($currentToken)) return null; + + $result = [ + 'syncToken' => $currentToken, + 'added' => [], + 'modified' => [], + 'deleted' => [], + ]; + + if ($syncToken) { + + $query = "SELECT uri, operation FROM " . $this->calendarChangesTableName . " WHERE synctoken >= ? AND synctoken < ? AND calendarid = ? ORDER BY synctoken"; + if ($limit > 0) $query .= " LIMIT " . (int)$limit; + + // Fetching all changes + $stmt = $this->pdo->prepare($query); + $stmt->execute([$syncToken, $currentToken, $calendarId]); + + $changes = []; + + // This loop ensures that any duplicates are overwritten, only the + // last change on a node is relevant. + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + + $changes[$row['uri']] = $row['operation']; + + } + + foreach ($changes as $uri => $operation) { + + switch ($operation) { + case 1 : + $result['added'][] = $uri; + break; + case 2 : + $result['modified'][] = $uri; + break; + case 3 : + $result['deleted'][] = $uri; + break; + } + + } + } else { + // No synctoken supplied, this is the initial sync. + $query = "SELECT uri FROM " . $this->calendarObjectTableName . " WHERE calendarid = ?"; + $stmt = $this->pdo->prepare($query); + $stmt->execute([$calendarId]); + + $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN); + } + return $result; + + } + + /** + * Adds a change record to the calendarchanges table. + * + * @param mixed $calendarId + * @param string $objectUri + * @param int $operation 1 = add, 2 = modify, 3 = delete. + * @return void + */ + protected function addChange($calendarId, $objectUri, $operation) { + + $stmt = $this->pdo->prepare('INSERT INTO ' . $this->calendarChangesTableName . ' (uri, synctoken, calendarid, operation) SELECT ?, synctoken, ?, ? FROM ' . $this->calendarTableName . ' WHERE id = ?'); + $stmt->execute([ + $objectUri, + $calendarId, + $operation, + $calendarId + ]); + $stmt = $this->pdo->prepare('UPDATE ' . $this->calendarTableName . ' SET synctoken = synctoken + 1 WHERE id = ?'); + $stmt->execute([ + $calendarId + ]); + + } + + /** + * Returns a list of subscriptions for a principal. + * + * Every subscription is an array with the following keys: + * * id, a unique id that will be used by other functions to modify the + * subscription. This can be the same as the uri or a database key. + * * uri. This is just the 'base uri' or 'filename' of the subscription. + * * principaluri. The owner of the subscription. Almost always the same as + * principalUri passed to this method. + * * source. Url to the actual feed + * + * Furthermore, all the subscription info must be returned too: + * + * 1. {DAV:}displayname + * 2. {http://apple.com/ns/ical/}refreshrate + * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos + * should not be stripped). + * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms + * should not be stripped). + * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if + * attachments should not be stripped). + * 7. {http://apple.com/ns/ical/}calendar-color + * 8. {http://apple.com/ns/ical/}calendar-order + * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set + * (should just be an instance of + * Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of + * default components). + * + * @param string $principalUri + * @return array + */ + function getSubscriptionsForUser($principalUri) { + + $fields = array_values($this->subscriptionPropertyMap); + $fields[] = 'id'; + $fields[] = 'uri'; + $fields[] = 'source'; + $fields[] = 'principaluri'; + $fields[] = 'lastmodified'; + + // Making fields a comma-delimited list + $fields = implode(', ', $fields); + $stmt = $this->pdo->prepare("SELECT " . $fields . " FROM " . $this->calendarSubscriptionsTableName . " WHERE principaluri = ? ORDER BY calendarorder ASC"); + $stmt->execute([$principalUri]); + + $subscriptions = []; + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + + $subscription = [ + 'id' => $row['id'], + 'uri' => $row['uri'], + 'principaluri' => $row['principaluri'], + 'source' => $row['source'], + 'lastmodified' => $row['lastmodified'], + + '{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet(['VTODO', 'VEVENT']), + ]; + + foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) { + if (!is_null($row[$dbName])) { + $subscription[$xmlName] = $row[$dbName]; + } + } + + $subscriptions[] = $subscription; + + } + + return $subscriptions; + + } + + /** + * Creates a new subscription for a principal. + * + * If the creation was a success, an id must be returned that can be used to reference + * this subscription in other methods, such as updateSubscription. + * + * @param string $principalUri + * @param string $uri + * @param array $properties + * @return mixed + */ + function createSubscription($principalUri, $uri, array $properties) { + + $fieldNames = [ + 'principaluri', + 'uri', + 'source', + 'lastmodified', + ]; + + if (!isset($properties['{http://calendarserver.org/ns/}source'])) { + throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions'); + } + + $values = [ + ':principaluri' => $principalUri, + ':uri' => $uri, + ':source' => $properties['{http://calendarserver.org/ns/}source']->getHref(), + ':lastmodified' => time(), + ]; + + foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) { + if (isset($properties[$xmlName])) { + + $values[':' . $dbName] = $properties[$xmlName]; + $fieldNames[] = $dbName; + } + } + + $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarSubscriptionsTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")"); + $stmt->execute($values); + + return $this->pdo->lastInsertId(); + + } + + /** + * Updates a subscription + * + * The list of mutations is stored in a Sabre\DAV\PropPatch object. + * To do the actual updates, you must tell this object which properties + * you're going to process with the handle() method. + * + * Calling the handle method is like telling the PropPatch object "I + * promise I can handle updating this property". + * + * Read the PropPatch documenation for more info and examples. + * + * @param mixed $subscriptionId + * @param \Sabre\DAV\PropPatch $propPatch + * @return void + */ + function updateSubscription($subscriptionId, DAV\PropPatch $propPatch) { + + $supportedProperties = array_keys($this->subscriptionPropertyMap); + $supportedProperties[] = '{http://calendarserver.org/ns/}source'; + + $propPatch->handle($supportedProperties, function($mutations) use ($subscriptionId) { + + $newValues = []; + + foreach ($mutations as $propertyName => $propertyValue) { + + if ($propertyName === '{http://calendarserver.org/ns/}source') { + $newValues['source'] = $propertyValue->getHref(); + } else { + $fieldName = $this->subscriptionPropertyMap[$propertyName]; + $newValues[$fieldName] = $propertyValue; + } + + } + + // Now we're generating the sql query. + $valuesSql = []; + foreach ($newValues as $fieldName => $value) { + $valuesSql[] = $fieldName . ' = ?'; + } + + $stmt = $this->pdo->prepare("UPDATE " . $this->calendarSubscriptionsTableName . " SET " . implode(', ', $valuesSql) . ", lastmodified = ? WHERE id = ?"); + $newValues['lastmodified'] = time(); + $newValues['id'] = $subscriptionId; + $stmt->execute(array_values($newValues)); + + return true; + + }); + + } + + /** + * Deletes a subscription + * + * @param mixed $subscriptionId + * @return void + */ + function deleteSubscription($subscriptionId) { + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarSubscriptionsTableName . ' WHERE id = ?'); + $stmt->execute([$subscriptionId]); + + } + + /** + * Returns a single scheduling object. + * + * The returned array should contain the following elements: + * * uri - A unique basename for the object. This will be used to + * construct a full uri. + * * calendardata - The iCalendar object + * * lastmodified - The last modification date. Can be an int for a unix + * timestamp, or a PHP DateTime object. + * * etag - A unique token that must change if the object changed. + * * size - The size of the object, in bytes. + * + * @param string $principalUri + * @param string $objectUri + * @return array + */ + function getSchedulingObject($principalUri, $objectUri) { + + $stmt = $this->pdo->prepare('SELECT uri, calendardata, lastmodified, etag, size FROM ' . $this->schedulingObjectTableName . ' WHERE principaluri = ? AND uri = ?'); + $stmt->execute([$principalUri, $objectUri]); + $row = $stmt->fetch(\PDO::FETCH_ASSOC); + + if (!$row) return null; + + return [ + 'uri' => $row['uri'], + 'calendardata' => $row['calendardata'], + 'lastmodified' => $row['lastmodified'], + 'etag' => '"' . $row['etag'] . '"', + 'size' => (int)$row['size'], + ]; + + } + + /** + * Returns all scheduling objects for the inbox collection. + * + * These objects should be returned as an array. Every item in the array + * should follow the same structure as returned from getSchedulingObject. + * + * The main difference is that 'calendardata' is optional. + * + * @param string $principalUri + * @return array + */ + function getSchedulingObjects($principalUri) { + + $stmt = $this->pdo->prepare('SELECT id, calendardata, uri, lastmodified, etag, size FROM ' . $this->schedulingObjectTableName . ' WHERE principaluri = ?'); + $stmt->execute([$principalUri]); + + $result = []; + foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) { + $result[] = [ + 'calendardata' => $row['calendardata'], + 'uri' => $row['uri'], + 'lastmodified' => $row['lastmodified'], + 'etag' => '"' . $row['etag'] . '"', + 'size' => (int)$row['size'], + ]; + } + + return $result; + + } + + /** + * Deletes a scheduling object + * + * @param string $principalUri + * @param string $objectUri + * @return void + */ + function deleteSchedulingObject($principalUri, $objectUri) { + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->schedulingObjectTableName . ' WHERE principaluri = ? AND uri = ?'); + $stmt->execute([$principalUri, $objectUri]); + + } + + /** + * Creates a new scheduling object. This should land in a users' inbox. + * + * @param string $principalUri + * @param string $objectUri + * @param string $objectData + * @return void + */ + function createSchedulingObject($principalUri, $objectUri, $objectData) { + + $stmt = $this->pdo->prepare('INSERT INTO ' . $this->schedulingObjectTableName . ' (principaluri, calendardata, uri, lastmodified, etag, size) VALUES (?, ?, ?, ?, ?, ?)'); + $stmt->execute([$principalUri, $objectData, $objectUri, time(), md5($objectData), strlen($objectData) ]); + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php b/vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php new file mode 100644 index 000000000..6ec0bf06b --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php @@ -0,0 +1,65 @@ + 'The current synctoken', + * 'added' => [ + * 'new.txt', + * ], + * 'modified' => [ + * 'modified.txt', + * ], + * 'deleted' => [ + * 'foo.php.bak', + * 'old.txt' + * ] + * ); + * + * The returned syncToken property should reflect the *current* syncToken + * of the calendar, as reported in the {http://sabredav.org/ns}sync-token + * property This is * needed here too, to ensure the operation is atomic. + * + * If the $syncToken argument is specified as null, this is an initial + * sync, and all members should be reported. + * + * The modified property is an array of nodenames that have changed since + * the last token. + * + * The deleted property is an array with nodenames, that have been deleted + * from collection. + * + * The $syncLevel argument is basically the 'depth' of the report. If it's + * 1, you only have to report changes that happened only directly in + * immediate descendants. If it's 2, it should also include changes from + * the nodes below the child collections. (grandchildren) + * + * The $limit argument allows a client to specify how many results should + * be returned at most. If the limit is not specified, it should be treated + * as infinite. + * + * If the limit (infinite or not) is higher than you're willing to return, + * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. + * + * If the syncToken is expired (due to data cleanup) or unknown, you must + * return null. + * + * The limit is 'suggestive'. You are free to ignore it. + * + * @param string $calendarId + * @param string $syncToken + * @param int $syncLevel + * @param int $limit + * @return array + */ + function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null); + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Calendar.php b/vendor/sabre/dav/lib/CalDAV/Calendar.php new file mode 100644 index 000000000..ff8e19b15 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Calendar.php @@ -0,0 +1,527 @@ +caldavBackend = $caldavBackend; + $this->calendarInfo = $calendarInfo; + + } + + /** + * Returns the name of the calendar + * + * @return string + */ + function getName() { + + return $this->calendarInfo['uri']; + + } + + /** + * Updates properties on this node. + * + * This method received a PropPatch object, which contains all the + * information about the update. + * + * To update specific properties, call the 'handle' method on this object. + * Read the PropPatch documentation for more information. + * + * @param PropPatch $propPatch + * @return void + */ + function propPatch(PropPatch $propPatch) { + + return $this->caldavBackend->updateCalendar($this->calendarInfo['id'], $propPatch); + + } + + /** + * Returns the list of properties + * + * @param array $requestedProperties + * @return array + */ + function getProperties($requestedProperties) { + + $response = []; + + foreach ($this->calendarInfo as $propName => $propValue) { + + if ($propName[0] === '{') + $response[$propName] = $this->calendarInfo[$propName]; + + } + return $response; + + } + + /** + * Returns a calendar object + * + * The contained calendar objects are for example Events or Todo's. + * + * @param string $name + * @return \Sabre\CalDAV\ICalendarObject + */ + function getChild($name) { + + $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name); + + if (!$obj) throw new DAV\Exception\NotFound('Calendar object not found'); + + $obj['acl'] = $this->getChildACL(); + + return new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj); + + } + + /** + * Returns the full list of calendar objects + * + * @return array + */ + function getChildren() { + + $objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id']); + $children = []; + foreach ($objs as $obj) { + $obj['acl'] = $this->getChildACL(); + $children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj); + } + return $children; + + } + + /** + * This method receives a list of paths in it's first argument. + * It must return an array with Node objects. + * + * If any children are not found, you do not have to return them. + * + * @param string[] $paths + * @return array + */ + function getMultipleChildren(array $paths) { + + $objs = $this->caldavBackend->getMultipleCalendarObjects($this->calendarInfo['id'], $paths); + $children = []; + foreach ($objs as $obj) { + $obj['acl'] = $this->getChildACL(); + $children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj); + } + return $children; + + } + + /** + * Checks if a child-node exists. + * + * @param string $name + * @return bool + */ + function childExists($name) { + + $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name); + if (!$obj) + return false; + else + return true; + + } + + /** + * Creates a new directory + * + * We actually block this, as subdirectories are not allowed in calendars. + * + * @param string $name + * @return void + */ + function createDirectory($name) { + + throw new DAV\Exception\MethodNotAllowed('Creating collections in calendar objects is not allowed'); + + } + + /** + * Creates a new file + * + * The contents of the new file must be a valid ICalendar string. + * + * @param string $name + * @param resource $calendarData + * @return string|null + */ + function createFile($name, $calendarData = null) { + + if (is_resource($calendarData)) { + $calendarData = stream_get_contents($calendarData); + } + return $this->caldavBackend->createCalendarObject($this->calendarInfo['id'], $name, $calendarData); + + } + + /** + * Deletes the calendar. + * + * @return void + */ + function delete() { + + $this->caldavBackend->deleteCalendar($this->calendarInfo['id']); + + } + + /** + * Renames the calendar. Note that most calendars use the + * {DAV:}displayname to display a name to display a name. + * + * @param string $newName + * @return void + */ + function setName($newName) { + + throw new DAV\Exception\MethodNotAllowed('Renaming calendars is not yet supported'); + + } + + /** + * Returns the last modification date as a unix timestamp. + * + * @return void + */ + function getLastModified() { + + return null; + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->calendarInfo['principaluri']; + + } + + /** + * Returns a group principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getGroup() { + + return null; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + $acl = [ + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner() . '/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner() . '/calendar-proxy-read', + 'protected' => true, + ], + [ + 'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ], + + ]; + if (empty($this->calendarInfo['{http://sabredav.org/ns}read-only'])) { + $acl[] = [ + 'privilege' => '{DAV:}write', + 'principal' => $this->getOwner(), + 'protected' => true, + ]; + $acl[] = [ + 'privilege' => '{DAV:}write', + 'principal' => $this->getOwner() . '/calendar-proxy-write', + 'protected' => true, + ]; + } + + return $acl; + + } + + /** + * This method returns the ACL's for calendar objects in this calendar. + * The result of this method automatically gets passed to the + * calendar-object nodes in the calendar. + * + * @return array + */ + function getChildACL() { + + $acl = [ + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner() . '/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner() . '/calendar-proxy-read', + 'protected' => true, + ], + + ]; + if (empty($this->calendarInfo['{http://sabredav.org/ns}read-only'])) { + $acl[] = [ + 'privilege' => '{DAV:}write', + 'principal' => $this->getOwner(), + 'protected' => true, + ]; + $acl[] = [ + 'privilege' => '{DAV:}write', + 'principal' => $this->getOwner() . '/calendar-proxy-write', + 'protected' => true, + ]; + + } + return $acl; + + } + + /** + * Updates the ACL + * + * This method will receive a list of new ACE's. + * + * @param array $acl + * @return void + */ + function setACL(array $acl) { + + throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported'); + + } + + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See \Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + function getSupportedPrivilegeSet() { + + $default = DAVACL\Plugin::getDefaultSupportedPrivilegeSet(); + + // We need to inject 'read-free-busy' in the tree, aggregated under + // {DAV:}read. + foreach ($default['aggregates'] as &$agg) { + + if ($agg['privilege'] !== '{DAV:}read') continue; + + $agg['aggregates'][] = [ + 'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy', + ]; + + } + return $default; + + } + + /** + * Performs a calendar-query on the contents of this calendar. + * + * The calendar-query is defined in RFC4791 : CalDAV. Using the + * calendar-query it is possible for a client to request a specific set of + * object, based on contents of iCalendar properties, date-ranges and + * iCalendar component types (VTODO, VEVENT). + * + * This method should just return a list of (relative) urls that match this + * query. + * + * The list of filters are specified as an array. The exact array is + * documented by Sabre\CalDAV\CalendarQueryParser. + * + * @param array $filters + * @return array + */ + function calendarQuery(array $filters) { + + return $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters); + + } + + /** + * This method returns the current sync-token for this collection. + * This can be any string. + * + * If null is returned from this function, the plugin assumes there's no + * sync information available. + * + * @return string|null + */ + function getSyncToken() { + + if ( + $this->caldavBackend instanceof Backend\SyncSupport && + isset($this->calendarInfo['{DAV:}sync-token']) + ) { + return $this->calendarInfo['{DAV:}sync-token']; + } + if ( + $this->caldavBackend instanceof Backend\SyncSupport && + isset($this->calendarInfo['{http://sabredav.org/ns}sync-token']) + ) { + return $this->calendarInfo['{http://sabredav.org/ns}sync-token']; + } + + } + + /** + * The getChanges method returns all the changes that have happened, since + * the specified syncToken and the current collection. + * + * This function should return an array, such as the following: + * + * [ + * 'syncToken' => 'The current synctoken', + * 'added' => [ + * 'new.txt', + * ], + * 'modified' => [ + * 'modified.txt', + * ], + * 'deleted' => [ + * 'foo.php.bak', + * 'old.txt' + * ] + * ]; + * + * The syncToken property should reflect the *current* syncToken of the + * collection, as reported getSyncToken(). This is needed here too, to + * ensure the operation is atomic. + * + * If the syncToken is specified as null, this is an initial sync, and all + * members should be reported. + * + * The modified property is an array of nodenames that have changed since + * the last token. + * + * The deleted property is an array with nodenames, that have been deleted + * from collection. + * + * The second argument is basically the 'depth' of the report. If it's 1, + * you only have to report changes that happened only directly in immediate + * descendants. If it's 2, it should also include changes from the nodes + * below the child collections. (grandchildren) + * + * The third (optional) argument allows a client to specify how many + * results should be returned at most. If the limit is not specified, it + * should be treated as infinite. + * + * If the limit (infinite or not) is higher than you're willing to return, + * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. + * + * If the syncToken is expired (due to data cleanup) or unknown, you must + * return null. + * + * The limit is 'suggestive'. You are free to ignore it. + * + * @param string $syncToken + * @param int $syncLevel + * @param int $limit + * @return array + */ + function getChanges($syncToken, $syncLevel, $limit = null) { + + if (!$this->caldavBackend instanceof Backend\SyncSupport) { + return null; + } + + return $this->caldavBackend->getChangesForCalendar( + $this->calendarInfo['id'], + $syncToken, + $syncLevel, + $limit + ); + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/CalendarHome.php b/vendor/sabre/dav/lib/CalDAV/CalendarHome.php new file mode 100644 index 000000000..a53f829e2 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/CalendarHome.php @@ -0,0 +1,430 @@ +caldavBackend = $caldavBackend; + $this->principalInfo = $principalInfo; + + } + + /** + * Returns the name of this object + * + * @return string + */ + function getName() { + + list(, $name) = URLUtil::splitPath($this->principalInfo['uri']); + return $name; + + } + + /** + * Updates the name of this object + * + * @param string $name + * @return void + */ + function setName($name) { + + throw new DAV\Exception\Forbidden(); + + } + + /** + * Deletes this object + * + * @return void + */ + function delete() { + + throw new DAV\Exception\Forbidden(); + + } + + /** + * Returns the last modification date + * + * @return int + */ + function getLastModified() { + + return null; + + } + + /** + * Creates a new file under this object. + * + * This is currently not allowed + * + * @param string $filename + * @param resource $data + * @return void + */ + function createFile($filename, $data = null) { + + throw new DAV\Exception\MethodNotAllowed('Creating new files in this collection is not supported'); + + } + + /** + * Creates a new directory under this object. + * + * This is currently not allowed. + * + * @param string $filename + * @return void + */ + function createDirectory($filename) { + + throw new DAV\Exception\MethodNotAllowed('Creating new collections in this collection is not supported'); + + } + + /** + * Returns a single calendar, by name + * + * @param string $name + * @return Calendar + */ + function getChild($name) { + + // Special nodes + if ($name === 'inbox' && $this->caldavBackend instanceof Backend\SchedulingSupport) { + return new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']); + } + if ($name === 'outbox' && $this->caldavBackend instanceof Backend\SchedulingSupport) { + return new Schedule\Outbox($this->principalInfo['uri']); + } + if ($name === 'notifications' && $this->caldavBackend instanceof Backend\NotificationSupport) { + return new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']); + } + + // Calendars + foreach ($this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']) as $calendar) { + if ($calendar['uri'] === $name) { + if ($this->caldavBackend instanceof Backend\SharingSupport) { + if (isset($calendar['{http://calendarserver.org/ns/}shared-url'])) { + return new SharedCalendar($this->caldavBackend, $calendar); + } else { + return new ShareableCalendar($this->caldavBackend, $calendar); + } + } else { + return new Calendar($this->caldavBackend, $calendar); + } + } + } + + if ($this->caldavBackend instanceof Backend\SubscriptionSupport) { + foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) { + if ($subscription['uri'] === $name) { + return new Subscriptions\Subscription($this->caldavBackend, $subscription); + } + } + + } + + throw new NotFound('Node with name \'' . $name . '\' could not be found'); + + } + + /** + * Checks if a calendar exists. + * + * @param string $name + * @return bool + */ + function childExists($name) { + + try { + return !!$this->getChild($name); + } catch (NotFound $e) { + return false; + } + + } + + /** + * Returns a list of calendars + * + * @return array + */ + function getChildren() { + + $calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']); + $objs = []; + foreach ($calendars as $calendar) { + if ($this->caldavBackend instanceof Backend\SharingSupport) { + if (isset($calendar['{http://calendarserver.org/ns/}shared-url'])) { + $objs[] = new SharedCalendar($this->caldavBackend, $calendar); + } else { + $objs[] = new ShareableCalendar($this->caldavBackend, $calendar); + } + } else { + $objs[] = new Calendar($this->caldavBackend, $calendar); + } + } + + if ($this->caldavBackend instanceof Backend\SchedulingSupport) { + $objs[] = new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']); + $objs[] = new Schedule\Outbox($this->principalInfo['uri']); + } + + // We're adding a notifications node, if it's supported by the backend. + if ($this->caldavBackend instanceof Backend\NotificationSupport) { + $objs[] = new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']); + } + + // If the backend supports subscriptions, we'll add those as well, + if ($this->caldavBackend instanceof Backend\SubscriptionSupport) { + foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) { + $objs[] = new Subscriptions\Subscription($this->caldavBackend, $subscription); + } + } + + return $objs; + + } + + /** + * Creates a new calendar or subscription. + * + * @param string $name + * @param MkCol $mkCol + * @throws DAV\Exception\InvalidResourceType + * @return void + */ + function createExtendedCollection($name, MkCol $mkCol) { + + $isCalendar = false; + $isSubscription = false; + foreach ($mkCol->getResourceType() as $rt) { + switch ($rt) { + case '{DAV:}collection' : + case '{http://calendarserver.org/ns/}shared-owner' : + // ignore + break; + case '{urn:ietf:params:xml:ns:caldav}calendar' : + $isCalendar = true; + break; + case '{http://calendarserver.org/ns/}subscribed' : + $isSubscription = true; + break; + default : + throw new DAV\Exception\InvalidResourceType('Unknown resourceType: ' . $rt); + } + } + + $properties = $mkCol->getRemainingValues(); + $mkCol->setRemainingResultCode(201); + + if ($isSubscription) { + if (!$this->caldavBackend instanceof Backend\SubscriptionSupport) { + throw new DAV\Exception\InvalidResourceType('This backend does not support subscriptions'); + } + $this->caldavBackend->createSubscription($this->principalInfo['uri'], $name, $properties); + + } elseif ($isCalendar) { + $this->caldavBackend->createCalendar($this->principalInfo['uri'], $name, $properties); + + } else { + throw new DAV\Exception\InvalidResourceType('You can only create calendars and subscriptions in this collection'); + + } + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->principalInfo['uri']; + + } + + /** + * Returns a group principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getGroup() { + + return null; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + return [ + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->principalInfo['uri'], + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => $this->principalInfo['uri'], + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->principalInfo['uri'] . '/calendar-proxy-read', + 'protected' => true, + ], + + ]; + + } + + /** + * Updates the ACL + * + * This method will receive a list of new ACE's. + * + * @param array $acl + * @return void + */ + function setACL(array $acl) { + + throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported'); + + } + + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + function getSupportedPrivilegeSet() { + + return null; + + } + + /** + * This method is called when a user replied to a request to share. + * + * This method should return the url of the newly created calendar if the + * share was accepted. + * + * @param string href The sharee who is replying (often a mailto: address) + * @param int status One of the SharingPlugin::STATUS_* constants + * @param string $calendarUri The url to the calendar thats being shared + * @param string $inReplyTo The unique id this message is a response to + * @param string $summary A description of the reply + * @return null|string + */ + function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null) { + + if (!$this->caldavBackend instanceof Backend\SharingSupport) { + throw new DAV\Exception\NotImplemented('Sharing support is not implemented by this backend.'); + } + + return $this->caldavBackend->shareReply($href, $status, $calendarUri, $inReplyTo, $summary); + + } + + /** + * Searches through all of a users calendars and calendar objects to find + * an object with a specific UID. + * + * This method should return the path to this object, relative to the + * calendar home, so this path usually only contains two parts: + * + * calendarpath/objectpath.ics + * + * If the uid is not found, return null. + * + * This method should only consider * objects that the principal owns, so + * any calendars owned by other principals that also appear in this + * collection should be ignored. + * + * @param string $uid + * @return string|null + */ + function getCalendarObjectByUID($uid) { + + return $this->caldavBackend->getCalendarObjectByUID($this->principalInfo['uri'], $uid); + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/CalendarObject.php b/vendor/sabre/dav/lib/CalDAV/CalendarObject.php new file mode 100644 index 000000000..393ca4cbd --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/CalendarObject.php @@ -0,0 +1,290 @@ +caldavBackend = $caldavBackend; + + if (!isset($objectData['uri'])) { + throw new \InvalidArgumentException('The objectData argument must contain an \'uri\' property'); + } + + $this->calendarInfo = $calendarInfo; + $this->objectData = $objectData; + + } + + /** + * Returns the uri for this object + * + * @return string + */ + function getName() { + + return $this->objectData['uri']; + + } + + /** + * Returns the ICalendar-formatted object + * + * @return string + */ + function get() { + + // Pre-populating the 'calendardata' is optional, if we don't have it + // already we fetch it from the backend. + if (!isset($this->objectData['calendardata'])) { + $this->objectData = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $this->objectData['uri']); + } + return $this->objectData['calendardata']; + + } + + /** + * Updates the ICalendar-formatted object + * + * @param string|resource $calendarData + * @return string + */ + function put($calendarData) { + + if (is_resource($calendarData)) { + $calendarData = stream_get_contents($calendarData); + } + $etag = $this->caldavBackend->updateCalendarObject($this->calendarInfo['id'], $this->objectData['uri'], $calendarData); + $this->objectData['calendardata'] = $calendarData; + $this->objectData['etag'] = $etag; + + return $etag; + + } + + /** + * Deletes the calendar object + * + * @return void + */ + function delete() { + + $this->caldavBackend->deleteCalendarObject($this->calendarInfo['id'], $this->objectData['uri']); + + } + + /** + * Returns the mime content-type + * + * @return string + */ + function getContentType() { + + $mime = 'text/calendar; charset=utf-8'; + if (isset($this->objectData['component']) && $this->objectData['component']) { + $mime .= '; component=' . $this->objectData['component']; + } + return $mime; + + } + + /** + * Returns an ETag for this object. + * + * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. + * + * @return string + */ + function getETag() { + + if (isset($this->objectData['etag'])) { + return $this->objectData['etag']; + } else { + return '"' . md5($this->get()) . '"'; + } + + } + + /** + * Returns the last modification date as a unix timestamp + * + * @return int + */ + function getLastModified() { + + return $this->objectData['lastmodified']; + + } + + /** + * Returns the size of this object in bytes + * + * @return int + */ + function getSize() { + + if (array_key_exists('size', $this->objectData)) { + return $this->objectData['size']; + } else { + return strlen($this->get()); + } + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->calendarInfo['principaluri']; + + } + + /** + * Returns a group principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getGroup() { + + return null; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + // An alternative acl may be specified in the object data. + if (isset($this->objectData['acl'])) { + return $this->objectData['acl']; + } + + // The default ACL + return [ + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->calendarInfo['principaluri'], + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => $this->calendarInfo['principaluri'], + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read', + 'protected' => true, + ], + + ]; + + } + + /** + * Updates the ACL + * + * This method will receive a list of new ACE's. + * + * @param array $acl + * @return void + */ + function setACL(array $acl) { + + throw new \Sabre\DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported'); + + } + + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See \Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + function getSupportedPrivilegeSet() { + + return null; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php b/vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php new file mode 100644 index 000000000..f3c7524d2 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php @@ -0,0 +1,375 @@ +name !== $filters['name']) { + return false; + } + + return + $this->validateCompFilters($vObject, $filters['comp-filters']) && + $this->validatePropFilters($vObject, $filters['prop-filters']); + + + } + + /** + * This method checks the validity of comp-filters. + * + * A list of comp-filters needs to be specified. Also the parent of the + * component we're checking should be specified, not the component to check + * itself. + * + * @param VObject\Component $parent + * @param array $filters + * @return bool + */ + protected function validateCompFilters(VObject\Component $parent, array $filters) { + + foreach ($filters as $filter) { + + $isDefined = isset($parent->{$filter['name']}); + + if ($filter['is-not-defined']) { + + if ($isDefined) { + return false; + } else { + continue; + } + + } + if (!$isDefined) { + return false; + } + + if ($filter['time-range']) { + foreach ($parent->{$filter['name']} as $subComponent) { + if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) { + continue 2; + } + } + return false; + } + + if (!$filter['comp-filters'] && !$filter['prop-filters']) { + continue; + } + + // If there are sub-filters, we need to find at least one component + // for which the subfilters hold true. + foreach ($parent->{$filter['name']} as $subComponent) { + + if ( + $this->validateCompFilters($subComponent, $filter['comp-filters']) && + $this->validatePropFilters($subComponent, $filter['prop-filters'])) { + // We had a match, so this comp-filter succeeds + continue 2; + } + + } + + // If we got here it means there were sub-comp-filters or + // sub-prop-filters and there was no match. This means this filter + // needs to return false. + return false; + + } + + // If we got here it means we got through all comp-filters alive so the + // filters were all true. + return true; + + } + + /** + * This method checks the validity of prop-filters. + * + * A list of prop-filters needs to be specified. Also the parent of the + * property we're checking should be specified, not the property to check + * itself. + * + * @param VObject\Component $parent + * @param array $filters + * @return bool + */ + protected function validatePropFilters(VObject\Component $parent, array $filters) { + + foreach ($filters as $filter) { + + $isDefined = isset($parent->{$filter['name']}); + + if ($filter['is-not-defined']) { + + if ($isDefined) { + return false; + } else { + continue; + } + + } + if (!$isDefined) { + return false; + } + + if ($filter['time-range']) { + foreach ($parent->{$filter['name']} as $subComponent) { + if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) { + continue 2; + } + } + return false; + } + + if (!$filter['param-filters'] && !$filter['text-match']) { + continue; + } + + // If there are sub-filters, we need to find at least one property + // for which the subfilters hold true. + foreach ($parent->{$filter['name']} as $subComponent) { + + if ( + $this->validateParamFilters($subComponent, $filter['param-filters']) && + (!$filter['text-match'] || $this->validateTextMatch($subComponent, $filter['text-match'])) + ) { + // We had a match, so this prop-filter succeeds + continue 2; + } + + } + + // If we got here it means there were sub-param-filters or + // text-match filters and there was no match. This means the + // filter needs to return false. + return false; + + } + + // If we got here it means we got through all prop-filters alive so the + // filters were all true. + return true; + + } + + /** + * This method checks the validity of param-filters. + * + * A list of param-filters needs to be specified. Also the parent of the + * parameter we're checking should be specified, not the parameter to check + * itself. + * + * @param VObject\Property $parent + * @param array $filters + * @return bool + */ + protected function validateParamFilters(VObject\Property $parent, array $filters) { + + foreach ($filters as $filter) { + + $isDefined = isset($parent[$filter['name']]); + + if ($filter['is-not-defined']) { + + if ($isDefined) { + return false; + } else { + continue; + } + + } + if (!$isDefined) { + return false; + } + + if (!$filter['text-match']) { + continue; + } + + // If there are sub-filters, we need to find at least one parameter + // for which the subfilters hold true. + foreach ($parent[$filter['name']]->getParts() as $paramPart) { + + if ($this->validateTextMatch($paramPart, $filter['text-match'])) { + // We had a match, so this param-filter succeeds + continue 2; + } + + } + + // If we got here it means there was a text-match filter and there + // were no matches. This means the filter needs to return false. + return false; + + } + + // If we got here it means we got through all param-filters alive so the + // filters were all true. + return true; + + } + + /** + * This method checks the validity of a text-match. + * + * A single text-match should be specified as well as the specific property + * or parameter we need to validate. + * + * @param VObject\Node|string $check Value to check against. + * @param array $textMatch + * @return bool + */ + protected function validateTextMatch($check, array $textMatch) { + + if ($check instanceof VObject\Node) { + $check = $check->getValue(); + } + + $isMatching = \Sabre\DAV\StringUtil::textMatch($check, $textMatch['value'], $textMatch['collation']); + + return ($textMatch['negate-condition'] xor $isMatching); + + } + + /** + * Validates if a component matches the given time range. + * + * This is all based on the rules specified in rfc4791, which are quite + * complex. + * + * @param VObject\Node $component + * @param DateTime $start + * @param DateTime $end + * @return bool + */ + protected function validateTimeRange(VObject\Node $component, $start, $end) { + + if (is_null($start)) { + $start = new DateTime('1900-01-01'); + } + if (is_null($end)) { + $end = new DateTime('3000-01-01'); + } + + switch ($component->name) { + + case 'VEVENT' : + case 'VTODO' : + case 'VJOURNAL' : + + return $component->isInTimeRange($start, $end); + + case 'VALARM' : + + // If the valarm is wrapped in a recurring event, we need to + // expand the recursions, and validate each. + // + // Our datamodel doesn't easily allow us to do this straight + // in the VALARM component code, so this is a hack, and an + // expensive one too. + if ($component->parent->name === 'VEVENT' && $component->parent->RRULE) { + + // Fire up the iterator! + $it = new VObject\Recur\EventIterator($component->parent->parent, (string)$component->parent->UID); + while ($it->valid()) { + $expandedEvent = $it->getEventObject(); + + // We need to check from these expanded alarms, which + // one is the first to trigger. Based on this, we can + // determine if we can 'give up' expanding events. + $firstAlarm = null; + if ($expandedEvent->VALARM !== null) { + foreach ($expandedEvent->VALARM as $expandedAlarm) { + + $effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime(); + if ($expandedAlarm->isInTimeRange($start, $end)) { + return true; + } + + if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') { + // This is an alarm with a non-relative trigger + // time, likely created by a buggy client. The + // implication is that every alarm in this + // recurring event trigger at the exact same + // time. It doesn't make sense to traverse + // further. + } else { + // We store the first alarm as a means to + // figure out when we can stop traversing. + if (!$firstAlarm || $effectiveTrigger < $firstAlarm) { + $firstAlarm = $effectiveTrigger; + } + } + } + } + if (is_null($firstAlarm)) { + // No alarm was found. + // + // Or technically: No alarm that will change for + // every instance of the recurrence was found, + // which means we can assume there was no match. + return false; + } + if ($firstAlarm > $end) { + return false; + } + $it->next(); + } + return false; + } else { + return $component->isInTimeRange($start, $end); + } + + case 'VFREEBUSY' : + throw new \Sabre\DAV\Exception\NotImplemented('time-range filters are currently not supported on ' . $component->name . ' components'); + + case 'COMPLETED' : + case 'CREATED' : + case 'DTEND' : + case 'DTSTAMP' : + case 'DTSTART' : + case 'DUE' : + case 'LAST-MODIFIED' : + return ($start <= $component->getDateTime() && $end >= $component->getDateTime()); + + + + default : + throw new \Sabre\DAV\Exception\BadRequest('You cannot create a time-range filter on a ' . $component->name . ' component'); + + } + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/CalendarRoot.php b/vendor/sabre/dav/lib/CalDAV/CalendarRoot.php new file mode 100644 index 000000000..0ac50e41d --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/CalendarRoot.php @@ -0,0 +1,80 @@ +caldavBackend = $caldavBackend; + + } + + /** + * Returns the nodename + * + * We're overriding this, because the default will be the 'principalPrefix', + * and we want it to be Sabre\CalDAV\Plugin::CALENDAR_ROOT + * + * @return string + */ + function getName() { + + return Plugin::CALENDAR_ROOT; + + } + + /** + * This method returns a node for a principal. + * + * The passed array contains principal information, and is guaranteed to + * at least contain a uri item. Other properties may or may not be + * supplied by the authentication backend. + * + * @param array $principal + * @return \Sabre\DAV\INode + */ + function getChildForPrincipal(array $principal) { + + return new CalendarHome($this->caldavBackend, $principal); + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php b/vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php new file mode 100644 index 000000000..5ce8a93f5 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php @@ -0,0 +1,35 @@ +ownerDocument; + + $np = $doc->createElementNS(CalDAV\Plugin::NS_CALDAV, 'cal:supported-calendar-component'); + $errorNode->appendChild($np); + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php b/vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php new file mode 100644 index 000000000..8c296d50f --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php @@ -0,0 +1,366 @@ +server = $server; + $server->on('method:GET', [$this, 'httpGet'], 90); + $server->on('browserButtonActions', function($path, $node, &$actions) { + if ($node instanceof ICalendar) { + $actions .= ''; + } + }); + + } + + /** + * Intercepts GET requests on calendar urls ending with ?export. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpGet(RequestInterface $request, ResponseInterface $response) { + + $queryParams = $request->getQueryParameters(); + if (!array_key_exists('export', $queryParams)) return; + + $path = $request->getPath(); + + $node = $this->server->getProperties($path, [ + '{DAV:}resourcetype', + '{DAV:}displayname', + '{http://sabredav.org/ns}sync-token', + '{DAV:}sync-token', + '{http://apple.com/ns/ical/}calendar-color', + ]); + + if (!isset($node['{DAV:}resourcetype']) || !$node['{DAV:}resourcetype']->is('{' . Plugin::NS_CALDAV . '}calendar')) { + return; + } + // Marking the transactionType, for logging purposes. + $this->server->transactionType = 'get-calendar-export'; + + $properties = $node; + + $start = null; + $end = null; + $expand = false; + $componentType = false; + if (isset($queryParams['start'])) { + if (!ctype_digit($queryParams['start'])) { + throw new BadRequest('The start= parameter must contain a unix timestamp'); + } + $start = DateTime::createFromFormat('U', $queryParams['start']); + } + if (isset($queryParams['end'])) { + if (!ctype_digit($queryParams['end'])) { + throw new BadRequest('The end= parameter must contain a unix timestamp'); + } + $end = DateTime::createFromFormat('U', $queryParams['end']); + } + if (isset($queryParams['expand']) && !!$queryParams['expand']) { + if (!$start || !$end) { + throw new BadRequest('If you\'d like to expand recurrences, you must specify both a start= and end= parameter.'); + } + $expand = true; + $componentType = 'VEVENT'; + } + if (isset($queryParams['componentType'])) { + if (!in_array($queryParams['componentType'], ['VEVENT', 'VTODO', 'VJOURNAL'])) { + throw new BadRequest('You are not allowed to search for components of type: ' . $queryParams['componentType'] . ' here'); + } + $componentType = $queryParams['componentType']; + } + + $format = \Sabre\HTTP\Util::Negotiate( + $request->getHeader('Accept'), + [ + 'text/calendar', + 'application/calendar+json', + ] + ); + + if (isset($queryParams['accept'])) { + if ($queryParams['accept'] === 'application/calendar+json' || $queryParams['accept'] === 'jcal') { + $format = 'application/calendar+json'; + } + } + if (!$format) { + $format = 'text/calendar'; + } + + $this->generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, $response); + + // Returning false to break the event chain + return false; + + } + + /** + * This method is responsible for generating the actual, full response. + * + * @param string $path + * @param DateTime|null $start + * @param DateTime|null $end + * @param bool $expand + * @param string $componentType + * @param string $format + * @param array $properties + * @param ResponseInterface $response + */ + protected function generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, ResponseInterface $response) { + + $calDataProp = '{' . Plugin::NS_CALDAV . '}calendar-data'; + + $blobs = []; + if ($start || $end || $componentType) { + + // If there was a start or end filter, we need to enlist + // calendarQuery for speed. + $calendarNode = $this->server->tree->getNodeForPath($path); + $queryResult = $calendarNode->calendarQuery([ + 'name' => 'VCALENDAR', + 'comp-filters' => [ + [ + 'name' => $componentType, + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => [ + 'start' => $start, + 'end' => $end, + ], + ], + ], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]); + + // queryResult is just a list of base urls. We need to prefix the + // calendar path. + $queryResult = array_map( + function($item) use ($path) { + return $path . '/' . $item; + }, + $queryResult + ); + $nodes = $this->server->getPropertiesForMultiplePaths($queryResult, [$calDataProp]); + unset($queryResult); + + } else { + $nodes = $this->server->getPropertiesForPath($path, [$calDataProp], 1); + } + + // Flattening the arrays + foreach ($nodes as $node) { + if (isset($node[200][$calDataProp])) { + $blobs[$node['href']] = $node[200][$calDataProp]; + } + } + unset($nodes); + + $mergedCalendar = $this->mergeObjects( + $properties, + $blobs + ); + + if ($expand) { + $calendarTimeZone = null; + // We're expanding, and for that we need to figure out the + // calendar's timezone. + $tzProp = '{' . Plugin::NS_CALDAV . '}calendar-timezone'; + $tzResult = $this->server->getProperties($path, [$tzProp]); + if (isset($tzResult[$tzProp])) { + // This property contains a VCALENDAR with a single + // VTIMEZONE. + $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]); + $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone(); + // Destroy circular references to PHP will GC the object. + $vtimezoneObj->destroy(); + unset($vtimezoneObj); + } else { + // Defaulting to UTC. + $calendarTimeZone = new DateTimeZone('UTC'); + } + + $mergedCalendar = $mergedCalendar->expand($start, $end, $calendarTimeZone); + } + + $response->setHeader('Content-Type', $format); + + switch ($format) { + case 'text/calendar' : + $mergedCalendar = $mergedCalendar->serialize(); + break; + case 'application/calendar+json' : + $mergedCalendar = json_encode($mergedCalendar->jsonSerialize()); + break; + } + + $response->setStatus(200); + $response->setBody($mergedCalendar); + + } + + /** + * Merges all calendar objects, and builds one big iCalendar blob. + * + * @param array $properties Some CalDAV properties + * @param array $inputObjects + * @return VObject\Component\VCalendar + */ + function mergeObjects(array $properties, array $inputObjects) { + + $calendar = new VObject\Component\VCalendar(); + $calendar->version = '2.0'; + if (DAV\Server::$exposeVersion) { + $calendar->prodid = '-//SabreDAV//SabreDAV ' . DAV\Version::VERSION . '//EN'; + } else { + $calendar->prodid = '-//SabreDAV//SabreDAV//EN'; + } + if (isset($properties['{DAV:}displayname'])) { + $calendar->{'X-WR-CALNAME'} = $properties['{DAV:}displayname']; + } + if (isset($properties['{http://apple.com/ns/ical/}calendar-color'])) { + $calendar->{'X-APPLE-CALENDAR-COLOR'} = $properties['{http://apple.com/ns/ical/}calendar-color']; + } + + $collectedTimezones = []; + + $timezones = []; + $objects = []; + + foreach ($inputObjects as $href => $inputObject) { + + $nodeComp = VObject\Reader::read($inputObject); + + foreach ($nodeComp->children() as $child) { + + switch ($child->name) { + case 'VEVENT' : + case 'VTODO' : + case 'VJOURNAL' : + $objects[] = clone $child; + break; + + // VTIMEZONE is special, because we need to filter out the duplicates + case 'VTIMEZONE' : + // Naively just checking tzid. + if (in_array((string)$child->TZID, $collectedTimezones)) continue; + + $timezones[] = clone $child; + $collectedTimezones[] = $child->TZID; + break; + + } + + } + // Destroy circular references to PHP will GC the object. + $nodeComp->destroy(); + unset($nodeComp); + + } + + foreach ($timezones as $tz) $calendar->add($tz); + foreach ($objects as $obj) $calendar->add($obj); + + return $calendar; + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using \Sabre\DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'ics-export'; + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Adds the ability to export CalDAV calendars as a single iCalendar file.', + 'link' => 'http://sabre.io/dav/ics-export-plugin/', + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/ICalendar.php b/vendor/sabre/dav/lib/CalDAV/ICalendar.php new file mode 100644 index 000000000..7cf4b1256 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/ICalendar.php @@ -0,0 +1,18 @@ +caldavBackend = $caldavBackend; + $this->principalUri = $principalUri; + + } + + /** + * Returns all notifications for a principal + * + * @return array + */ + function getChildren() { + + $children = []; + $notifications = $this->caldavBackend->getNotificationsForPrincipal($this->principalUri); + + foreach ($notifications as $notification) { + + $children[] = new Node( + $this->caldavBackend, + $this->principalUri, + $notification + ); + } + + return $children; + + } + + /** + * Returns the name of this object + * + * @return string + */ + function getName() { + + return 'notifications'; + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->principalUri; + + } + + /** + * Returns a group principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getGroup() { + + return null; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + return [ + [ + 'principal' => $this->getOwner(), + 'privilege' => '{DAV:}read', + 'protected' => true, + ], + [ + 'principal' => $this->getOwner(), + 'privilege' => '{DAV:}write', + 'protected' => true, + ] + ]; + + } + + /** + * Updates the ACL + * + * This method will receive a list of new ACE's as an array argument. + * + * @param array $acl + * @return void + */ + function setACL(array $acl) { + + throw new DAV\Exception\NotImplemented('Updating ACLs is not implemented here'); + + } + + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + function getSupportedPrivilegeSet() { + + return null; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Notifications/ICollection.php b/vendor/sabre/dav/lib/CalDAV/Notifications/ICollection.php new file mode 100644 index 000000000..008e87435 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Notifications/ICollection.php @@ -0,0 +1,23 @@ +caldavBackend = $caldavBackend; + $this->principalUri = $principalUri; + $this->notification = $notification; + + } + + /** + * Returns the path name for this notification + * + * @return id + */ + function getName() { + + return $this->notification->getId() . '.xml'; + + } + + /** + * Returns the etag for the notification. + * + * The etag must be surrounded by litteral double-quotes. + * + * @return string + */ + function getETag() { + + return $this->notification->getETag(); + + } + + /** + * This method must return an xml element, using the + * Sabre\CalDAV\Notifications\INotificationType classes. + * + * @return INotificationType + */ + function getNotificationType() { + + return $this->notification; + + } + + /** + * Deletes this notification + * + * @return void + */ + function delete() { + + $this->caldavBackend->deleteNotification($this->getOwner(), $this->notification); + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->principalUri; + + } + + /** + * Returns a group principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getGroup() { + + return null; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + return [ + [ + 'principal' => $this->getOwner(), + 'privilege' => '{DAV:}read', + 'protected' => true, + ], + [ + 'principal' => $this->getOwner(), + 'privilege' => '{DAV:}write', + 'protected' => true, + ] + ]; + + } + + /** + * Updates the ACL + * + * This method will receive a list of new ACE's as an array argument. + * + * @param array $acl + * @return void + */ + function setACL(array $acl) { + + throw new DAV\Exception\NotImplemented('Updating ACLs is not implemented here'); + + } + + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + function getSupportedPrivilegeSet() { + + return null; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.php b/vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.php new file mode 100644 index 000000000..546bf927f --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.php @@ -0,0 +1,180 @@ +server = $server; + $server->on('method:GET', [$this, 'httpGet'], 90); + $server->on('propFind', [$this, 'propFind']); + + $server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs'; + $server->resourceTypeMapping['\\Sabre\\CalDAV\\Notifications\\ICollection'] = '{' . self::NS_CALENDARSERVER . '}notification'; + + array_push($server->protectedProperties, + '{' . self::NS_CALENDARSERVER . '}notification-URL', + '{' . self::NS_CALENDARSERVER . '}notificationtype' + ); + + } + + /** + * PropFind + * + * @param PropFind $propFind + * @param BaseINode $node + * @return void + */ + function propFind(PropFind $propFind, BaseINode $node) { + + $caldavPlugin = $this->server->getPlugin('caldav'); + + if ($node instanceof DAVACL\IPrincipal) { + + $principalUrl = $node->getPrincipalUrl(); + + // notification-URL property + $propFind->handle('{' . self::NS_CALENDARSERVER . '}notification-URL', function() use ($principalUrl, $caldavPlugin) { + + $notificationPath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl) . '/notifications/'; + return new DAV\Xml\Property\Href($notificationPath); + + }); + + } + + if ($node instanceof INode) { + + $propFind->handle( + '{' . self::NS_CALENDARSERVER . '}notificationtype', + [$node, 'getNotificationType'] + ); + + } + + } + + /** + * This event is triggered before the usual GET request handler. + * + * We use this to intercept GET calls to notification nodes, and return the + * proper response. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function httpGet(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + try { + $node = $this->server->tree->getNodeForPath($path); + } catch (DAV\Exception\NotFound $e) { + return; + } + + if (!$node instanceof INode) + return; + + $writer = $this->server->xml->getWriter(); + $writer->contextUri = $this->server->getBaseUri(); + $writer->openMemory(); + $writer->startDocument('1.0', 'UTF-8'); + $writer->startElement('{http://calendarserver.org/ns/}notification'); + $node->getNotificationType()->xmlSerializeFull($writer); + $writer->endElement(); + + $response->setHeader('Content-Type', 'application/xml'); + $response->setHeader('ETag', $node->getETag()); + $response->setStatus(200); + $response->setBody($writer->outputMemory()); + + // Return false to break the event chain. + return false; + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Adds support for caldav-notifications, which is required to enable caldav-sharing.', + 'link' => 'http://sabre.io/dav/caldav-sharing/', + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Plugin.php b/vendor/sabre/dav/lib/CalDAV/Plugin.php new file mode 100644 index 000000000..663490023 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Plugin.php @@ -0,0 +1,1025 @@ +server->tree->getNodeForPath($parent); + + if ($node instanceof DAV\IExtendedCollection) { + try { + $node->getChild($name); + } catch (DAV\Exception\NotFound $e) { + return ['MKCALENDAR']; + } + } + return []; + + } + + /** + * Returns the path to a principal's calendar home. + * + * The return url must not end with a slash. + * This function should return null in case a principal did not have + * a calendar home. + * + * @param string $principalUrl + * @return string + */ + function getCalendarHomeForPrincipal($principalUrl) { + + // The default behavior for most sabre/dav servers is that there is a + // principals root node, which contains users directly under it. + // + // This function assumes that there are two components in a principal + // path. If there's more, we don't return a calendar home. This + // excludes things like the calendar-proxy-read principal (which it + // should). + $parts = explode('/', trim($principalUrl, '/')); + if (count($parts) !== 2) return; + if ($parts[0] !== 'principals') return; + + return self::CALENDAR_ROOT . '/' . $parts[1]; + + } + + /** + * Returns a list of features for the DAV: HTTP header. + * + * @return array + */ + function getFeatures() { + + return ['calendar-access', 'calendar-proxy']; + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'caldav'; + + } + + /** + * Returns a list of reports this plugin supports. + * + * This will be used in the {DAV:}supported-report-set property. + * Note that you still need to subscribe to the 'report' event to actually + * implement them + * + * @param string $uri + * @return array + */ + function getSupportedReportSet($uri) { + + $node = $this->server->tree->getNodeForPath($uri); + + $reports = []; + if ($node instanceof ICalendarObjectContainer || $node instanceof ICalendarObject) { + $reports[] = '{' . self::NS_CALDAV . '}calendar-multiget'; + $reports[] = '{' . self::NS_CALDAV . '}calendar-query'; + } + if ($node instanceof ICalendar) { + $reports[] = '{' . self::NS_CALDAV . '}free-busy-query'; + } + // iCal has a bug where it assumes that sync support is enabled, only + // if we say we support it on the calendar-home, even though this is + // not actually the case. + if ($node instanceof CalendarHome && $this->server->getPlugin('sync')) { + $reports[] = '{DAV:}sync-collection'; + } + return $reports; + + } + + /** + * Initializes the plugin + * + * @param DAV\Server $server + * @return void + */ + function initialize(DAV\Server $server) { + + $this->server = $server; + + $server->on('method:MKCALENDAR', [$this, 'httpMkCalendar']); + $server->on('report', [$this, 'report']); + $server->on('propFind', [$this, 'propFind']); + $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']); + $server->on('beforeCreateFile', [$this, 'beforeCreateFile']); + $server->on('beforeWriteContent', [$this, 'beforeWriteContent']); + $server->on('afterMethod:GET', [$this, 'httpAfterGET']); + + $server->xml->namespaceMap[self::NS_CALDAV] = 'cal'; + $server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs'; + + $server->xml->elementMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet'; + $server->xml->elementMap['{' . self::NS_CALDAV . '}calendar-query'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarQueryReport'; + $server->xml->elementMap['{' . self::NS_CALDAV . '}calendar-multiget'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarMultiGetReport'; + $server->xml->elementMap['{' . self::NS_CALDAV . '}free-busy-query'] = 'Sabre\\CalDAV\\Xml\\Request\\FreeBusyQueryReport'; + $server->xml->elementMap['{' . self::NS_CALDAV . '}mkcalendar'] = 'Sabre\\CalDAV\\Xml\\Request\\MkCalendar'; + $server->xml->elementMap['{' . self::NS_CALDAV . '}schedule-calendar-transp'] = 'Sabre\\CalDAV\\Xml\\Property\\ScheduleCalendarTransp'; + $server->xml->elementMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet'; + + $server->resourceTypeMapping['\\Sabre\\CalDAV\\ICalendar'] = '{urn:ietf:params:xml:ns:caldav}calendar'; + + $server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyRead'] = '{http://calendarserver.org/ns/}calendar-proxy-read'; + $server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyWrite'] = '{http://calendarserver.org/ns/}calendar-proxy-write'; + + array_push($server->protectedProperties, + + '{' . self::NS_CALDAV . '}supported-calendar-component-set', + '{' . self::NS_CALDAV . '}supported-calendar-data', + '{' . self::NS_CALDAV . '}max-resource-size', + '{' . self::NS_CALDAV . '}min-date-time', + '{' . self::NS_CALDAV . '}max-date-time', + '{' . self::NS_CALDAV . '}max-instances', + '{' . self::NS_CALDAV . '}max-attendees-per-instance', + '{' . self::NS_CALDAV . '}calendar-home-set', + '{' . self::NS_CALDAV . '}supported-collation-set', + '{' . self::NS_CALDAV . '}calendar-data', + + // CalendarServer extensions + '{' . self::NS_CALENDARSERVER . '}getctag', + '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for', + '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for' + + ); + + if ($aclPlugin = $server->getPlugin('acl')) { + $aclPlugin->principalSearchPropertySet['{' . self::NS_CALDAV . '}calendar-user-address-set'] = 'Calendar address'; + } + } + + /** + * This functions handles REPORT requests specific to CalDAV + * + * @param string $reportName + * @param mixed $report + * @return bool + */ + function report($reportName, $report) { + + switch ($reportName) { + case '{' . self::NS_CALDAV . '}calendar-multiget' : + $this->server->transactionType = 'report-calendar-multiget'; + $this->calendarMultiGetReport($report); + return false; + case '{' . self::NS_CALDAV . '}calendar-query' : + $this->server->transactionType = 'report-calendar-query'; + $this->calendarQueryReport($report); + return false; + case '{' . self::NS_CALDAV . '}free-busy-query' : + $this->server->transactionType = 'report-free-busy-query'; + $this->freeBusyQueryReport($report); + return false; + + } + + + } + + /** + * This function handles the MKCALENDAR HTTP method, which creates + * a new calendar. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpMkCalendar(RequestInterface $request, ResponseInterface $response) { + + $body = $request->getBodyAsString(); + $path = $request->getPath(); + + $properties = []; + + if ($body) { + + try { + $mkcalendar = $this->server->xml->expect( + '{urn:ietf:params:xml:ns:caldav}mkcalendar', + $body + ); + } catch (\Sabre\Xml\ParseException $e) { + throw new BadRequest($e->getMessage(), null, $e); + } + $properties = $mkcalendar->getProperties(); + + } + + // iCal abuses MKCALENDAR since iCal 10.9.2 to create server-stored + // subscriptions. Before that it used MKCOL which was the correct way + // to do this. + // + // If the body had a {DAV:}resourcetype, it means we stumbled upon this + // request, and we simply use it instead of the pre-defined list. + if (isset($properties['{DAV:}resourcetype'])) { + $resourceType = $properties['{DAV:}resourcetype']->getValue(); + } else { + $resourceType = ['{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar']; + } + + $this->server->createCollection($path, new MkCol($resourceType, $properties)); + + $this->server->httpResponse->setStatus(201); + $this->server->httpResponse->setHeader('Content-Length', 0); + + // This breaks the method chain. + return false; + } + + /** + * PropFind + * + * This method handler is invoked before any after properties for a + * resource are fetched. This allows us to add in any CalDAV specific + * properties. + * + * @param DAV\PropFind $propFind + * @param DAV\INode $node + * @return void + */ + function propFind(DAV\PropFind $propFind, DAV\INode $node) { + + $ns = '{' . self::NS_CALDAV . '}'; + + if ($node instanceof ICalendarObjectContainer) { + + $propFind->handle($ns . 'max-resource-size', $this->maxResourceSize); + $propFind->handle($ns . 'supported-calendar-data', function() { + return new Xml\Property\SupportedCalendarData(); + }); + $propFind->handle($ns . 'supported-collation-set', function() { + return new Xml\Property\SupportedCollationSet(); + }); + + } + + if ($node instanceof DAVACL\IPrincipal) { + + $principalUrl = $node->getPrincipalUrl(); + + $propFind->handle('{' . self::NS_CALDAV . '}calendar-home-set', function() use ($principalUrl) { + + $calendarHomePath = $this->getCalendarHomeForPrincipal($principalUrl); + if (is_null($calendarHomePath)) return null; + return new Href($calendarHomePath . '/'); + + }); + // The calendar-user-address-set property is basically mapped to + // the {DAV:}alternate-URI-set property. + $propFind->handle('{' . self::NS_CALDAV . '}calendar-user-address-set', function() use ($node) { + $addresses = $node->getAlternateUriSet(); + $addresses[] = $this->server->getBaseUri() . $node->getPrincipalUrl() . '/'; + return new Href($addresses, false); + }); + // For some reason somebody thought it was a good idea to add + // another one of these properties. We're supporting it too. + $propFind->handle('{' . self::NS_CALENDARSERVER . '}email-address-set', function() use ($node) { + $addresses = $node->getAlternateUriSet(); + $emails = []; + foreach ($addresses as $address) { + if (substr($address, 0, 7) === 'mailto:') { + $emails[] = substr($address, 7); + } + } + return new Xml\Property\EmailAddressSet($emails); + }); + + // These two properties are shortcuts for ical to easily find + // other principals this principal has access to. + $propRead = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for'; + $propWrite = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for'; + + if ($propFind->getStatus($propRead) === 404 || $propFind->getStatus($propWrite) === 404) { + + $aclPlugin = $this->server->getPlugin('acl'); + $membership = $aclPlugin->getPrincipalMembership($propFind->getPath()); + $readList = []; + $writeList = []; + + foreach ($membership as $group) { + + $groupNode = $this->server->tree->getNodeForPath($group); + + $listItem = Uri\split($group)[0] . '/'; + + // If the node is either ap proxy-read or proxy-write + // group, we grab the parent principal and add it to the + // list. + if ($groupNode instanceof Principal\IProxyRead) { + $readList[] = $listItem; + } + if ($groupNode instanceof Principal\IProxyWrite) { + $writeList[] = $listItem; + } + + } + + $propFind->set($propRead, new Href($readList)); + $propFind->set($propWrite, new Href($writeList)); + + } + + } // instanceof IPrincipal + + if ($node instanceof ICalendarObject) { + + // The calendar-data property is not supposed to be a 'real' + // property, but in large chunks of the spec it does act as such. + // Therefore we simply expose it as a property. + $propFind->handle('{' . self::NS_CALDAV . '}calendar-data', function() use ($node) { + $val = $node->get(); + if (is_resource($val)) + $val = stream_get_contents($val); + + // Taking out \r to not screw up the xml output + return str_replace("\r", "", $val); + + }); + + } + + } + + /** + * This function handles the calendar-multiget REPORT. + * + * This report is used by the client to fetch the content of a series + * of urls. Effectively avoiding a lot of redundant requests. + * + * @param CalendarMultiGetReport $report + * @return void + */ + function calendarMultiGetReport($report) { + + $needsJson = $report->contentType === 'application/calendar+json'; + + $timeZones = []; + $propertyList = []; + + $paths = array_map( + [$this->server, 'calculateUri'], + $report->hrefs + ); + + foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $uri => $objProps) { + + if (($needsJson || $report->expand) && isset($objProps[200]['{' . self::NS_CALDAV . '}calendar-data'])) { + $vObject = VObject\Reader::read($objProps[200]['{' . self::NS_CALDAV . '}calendar-data']); + + if ($report->expand) { + // We're expanding, and for that we need to figure out the + // calendar's timezone. + list($calendarPath) = Uri\split($uri); + if (!isset($timeZones[$calendarPath])) { + // Checking the calendar-timezone property. + $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone'; + $tzResult = $this->server->getProperties($calendarPath, [$tzProp]); + if (isset($tzResult[$tzProp])) { + // This property contains a VCALENDAR with a single + // VTIMEZONE. + $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]); + $timeZone = $vtimezoneObj->VTIMEZONE->getTimeZone(); + } else { + // Defaulting to UTC. + $timeZone = new DateTimeZone('UTC'); + } + $timeZones[$calendarPath] = $timeZone; + } + + $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $timeZones[$calendarPath]); + } + if ($needsJson) { + $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize()); + } else { + $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); + } + // Destroy circular references so PHP will garbage collect the + // object. + $vObject->destroy(); + } + + $propertyList[] = $objProps; + + } + + $prefer = $this->server->getHTTPPrefer(); + + $this->server->httpResponse->setStatus(207); + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer'); + $this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, $prefer['return'] === 'minimal')); + + } + + /** + * This function handles the calendar-query REPORT + * + * This report is used by clients to request calendar objects based on + * complex conditions. + * + * @param Xml\Request\CalendarQueryReport $report + * @return void + */ + function calendarQueryReport($report) { + + $path = $this->server->getRequestUri(); + + $needsJson = $report->contentType === 'application/calendar+json'; + + $node = $this->server->tree->getNodeForPath($this->server->getRequestUri()); + $depth = $this->server->getHTTPDepth(0); + + // The default result is an empty array + $result = []; + + $calendarTimeZone = null; + if ($report->expand) { + // We're expanding, and for that we need to figure out the + // calendar's timezone. + $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone'; + $tzResult = $this->server->getProperties($path, [$tzProp]); + if (isset($tzResult[$tzProp])) { + // This property contains a VCALENDAR with a single + // VTIMEZONE. + $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]); + $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone(); + + // Destroy circular references so PHP will garbage collect the + // object. + $vtimezoneObj->destroy(); + } else { + // Defaulting to UTC. + $calendarTimeZone = new DateTimeZone('UTC'); + } + } + + // The calendarobject was requested directly. In this case we handle + // this locally. + if ($depth == 0 && $node instanceof ICalendarObject) { + + $requestedCalendarData = true; + $requestedProperties = $report->properties; + + if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) { + + // We always retrieve calendar-data, as we need it for filtering. + $requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data'; + + // If calendar-data wasn't explicitly requested, we need to remove + // it after processing. + $requestedCalendarData = false; + } + + $properties = $this->server->getPropertiesForPath( + $path, + $requestedProperties, + 0 + ); + + // This array should have only 1 element, the first calendar + // object. + $properties = current($properties); + + // If there wasn't any calendar-data returned somehow, we ignore + // this. + if (isset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) { + + $validator = new CalendarQueryValidator(); + + $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); + if ($validator->validate($vObject, $report->filters)) { + + // If the client didn't require the calendar-data property, + // we won't give it back. + if (!$requestedCalendarData) { + unset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); + } else { + + + if ($report->expand) { + $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone); + } + if ($needsJson) { + $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize()); + } elseif ($report->expand) { + $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); + } + } + + $result = [$properties]; + + } + // Destroy circular references so PHP will garbage collect the + // object. + $vObject->destroy(); + + } + + } + + if ($node instanceof ICalendarObjectContainer && $depth === 0) { + + if (strpos($this->server->httpRequest->getHeader('User-Agent'), 'MSFT-') === 0) { + // Microsoft clients incorrectly supplied depth as 0, when it actually + // should have set depth to 1. We're implementing a workaround here + // to deal with this. + // + // This targets at least the following clients: + // Windows 10 + // Windows Phone 8, 10 + $depth = 1; + } else { + throw new BadRequest('A calendar-query REPORT on a calendar with a Depth: 0 is undefined. Set Depth to 1'); + } + + } + + // If we're dealing with a calendar, the calendar itself is responsible + // for the calendar-query. + if ($node instanceof ICalendarObjectContainer && $depth == 1) { + + $nodePaths = $node->calendarQuery($report->filters); + + foreach ($nodePaths as $path) { + + list($properties) = + $this->server->getPropertiesForPath($this->server->getRequestUri() . '/' . $path, $report->properties); + + if (($needsJson || $report->expand)) { + $vObject = VObject\Reader::read($properties[200]['{' . self::NS_CALDAV . '}calendar-data']); + + if ($report->expand) { + $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone); + } + + if ($needsJson) { + $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize()); + } else { + $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); + } + + // Destroy circular references so PHP will garbage collect the + // object. + $vObject->destroy(); + } + $result[] = $properties; + + } + + } + + $prefer = $this->server->getHTTPPrefer(); + + $this->server->httpResponse->setStatus(207); + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer'); + $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return'] === 'minimal')); + + } + + /** + * This method is responsible for parsing the request and generating the + * response for the CALDAV:free-busy-query REPORT. + * + * @param Xml\Request\FreeBusyQueryReport $report + * @return void + */ + protected function freeBusyQueryReport(Xml\Request\FreeBusyQueryReport $report) { + + $uri = $this->server->getRequestUri(); + + $acl = $this->server->getPlugin('acl'); + if ($acl) { + $acl->checkPrivileges($uri, '{' . self::NS_CALDAV . '}read-free-busy'); + } + + $calendar = $this->server->tree->getNodeForPath($uri); + if (!$calendar instanceof ICalendar) { + throw new DAV\Exception\NotImplemented('The free-busy-query REPORT is only implemented on calendars'); + } + + $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone'; + + // Figuring out the default timezone for the calendar, for floating + // times. + $calendarProps = $this->server->getProperties($uri, [$tzProp]); + + if (isset($calendarProps[$tzProp])) { + $vtimezoneObj = VObject\Reader::read($calendarProps[$tzProp]); + $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone(); + // Destroy circular references so PHP will garbage collect the object. + $vtimezoneObj->destroy(); + } else { + $calendarTimeZone = new DateTimeZone('UTC'); + } + + // Doing a calendar-query first, to make sure we get the most + // performance. + $urls = $calendar->calendarQuery([ + 'name' => 'VCALENDAR', + 'comp-filters' => [ + [ + 'name' => 'VEVENT', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => [ + 'start' => $report->start, + 'end' => $report->end, + ], + ], + ], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]); + + $objects = array_map(function($url) use ($calendar) { + $obj = $calendar->getChild($url)->get(); + return $obj; + }, $urls); + + $generator = new VObject\FreeBusyGenerator(); + $generator->setObjects($objects); + $generator->setTimeRange($report->start, $report->end); + $generator->setTimeZone($calendarTimeZone); + $result = $generator->getResult(); + $result = $result->serialize(); + + $this->server->httpResponse->setStatus(200); + $this->server->httpResponse->setHeader('Content-Type', 'text/calendar'); + $this->server->httpResponse->setHeader('Content-Length', strlen($result)); + $this->server->httpResponse->setBody($result); + + } + + /** + * This method is triggered before a file gets updated with new content. + * + * This plugin uses this method to ensure that CalDAV objects receive + * valid calendar data. + * + * @param string $path + * @param DAV\IFile $node + * @param resource $data + * @param bool $modified Should be set to true, if this event handler + * changed &$data. + * @return void + */ + function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified) { + + if (!$node instanceof ICalendarObject) + return; + + // We're onyl interested in ICalendarObject nodes that are inside of a + // real calendar. This is to avoid triggering validation and scheduling + // for non-calendars (such as an inbox). + list($parent) = Uri\split($path); + $parentNode = $this->server->tree->getNodeForPath($parent); + + if (!$parentNode instanceof ICalendar) + return; + + $this->validateICalendar( + $data, + $path, + $modified, + $this->server->httpRequest, + $this->server->httpResponse, + false + ); + + } + + /** + * This method is triggered before a new file is created. + * + * This plugin uses this method to ensure that newly created calendar + * objects contain valid calendar data. + * + * @param string $path + * @param resource $data + * @param DAV\ICollection $parentNode + * @param bool $modified Should be set to true, if this event handler + * changed &$data. + * @return void + */ + function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified) { + + if (!$parentNode instanceof ICalendar) + return; + + $this->validateICalendar( + $data, + $path, + $modified, + $this->server->httpRequest, + $this->server->httpResponse, + true + ); + + } + + /** + * Checks if the submitted iCalendar data is in fact, valid. + * + * An exception is thrown if it's not. + * + * @param resource|string $data + * @param string $path + * @param bool $modified Should be set to true, if this event handler + * changed &$data. + * @param RequestInterface $request The http request. + * @param ResponseInterface $response The http response. + * @param bool $isNew Is the item a new one, or an update. + * @return void + */ + protected function validateICalendar(&$data, $path, &$modified, RequestInterface $request, ResponseInterface $response, $isNew) { + + // If it's a stream, we convert it to a string first. + if (is_resource($data)) { + $data = stream_get_contents($data); + } + + $before = md5($data); + // Converting the data to unicode, if needed. + $data = DAV\StringUtil::ensureUTF8($data); + + if ($before !== md5($data)) $modified = true; + + try { + + // If the data starts with a [, we can reasonably assume we're dealing + // with a jCal object. + if (substr($data, 0, 1) === '[') { + $vobj = VObject\Reader::readJson($data); + + // Converting $data back to iCalendar, as that's what we + // technically support everywhere. + $data = $vobj->serialize(); + $modified = true; + } else { + $vobj = VObject\Reader::read($data); + } + + } catch (VObject\ParseException $e) { + + throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid iCalendar 2.0 data. Parse error: ' . $e->getMessage()); + + } + + if ($vobj->name !== 'VCALENDAR') { + throw new DAV\Exception\UnsupportedMediaType('This collection can only support iCalendar objects.'); + } + + $sCCS = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'; + + // Get the Supported Components for the target calendar + list($parentPath) = Uri\split($path); + $calendarProperties = $this->server->getProperties($parentPath, [$sCCS]); + + if (isset($calendarProperties[$sCCS])) { + $supportedComponents = $calendarProperties[$sCCS]->getValue(); + } else { + $supportedComponents = ['VJOURNAL', 'VTODO', 'VEVENT']; + } + + $foundType = null; + $foundUID = null; + foreach ($vobj->getComponents() as $component) { + switch ($component->name) { + case 'VTIMEZONE' : + continue 2; + case 'VEVENT' : + case 'VTODO' : + case 'VJOURNAL' : + if (is_null($foundType)) { + $foundType = $component->name; + if (!in_array($foundType, $supportedComponents)) { + throw new Exception\InvalidComponentType('This calendar only supports ' . implode(', ', $supportedComponents) . '. We found a ' . $foundType); + } + if (!isset($component->UID)) { + throw new DAV\Exception\BadRequest('Every ' . $component->name . ' component must have an UID'); + } + $foundUID = (string)$component->UID; + } else { + if ($foundType !== $component->name) { + throw new DAV\Exception\BadRequest('A calendar object must only contain 1 component. We found a ' . $component->name . ' as well as a ' . $foundType); + } + if ($foundUID !== (string)$component->UID) { + throw new DAV\Exception\BadRequest('Every ' . $component->name . ' in this object must have identical UIDs'); + } + } + break; + default : + throw new DAV\Exception\BadRequest('You are not allowed to create components of type: ' . $component->name . ' here'); + + } + } + if (!$foundType) + throw new DAV\Exception\BadRequest('iCalendar object must contain at least 1 of VEVENT, VTODO or VJOURNAL'); + + // We use an extra variable to allow event handles to tell us wether + // the object was modified or not. + // + // This helps us determine if we need to re-serialize the object. + $subModified = false; + + $this->server->emit( + 'calendarObjectChange', + [ + $request, + $response, + $vobj, + $parentPath, + &$subModified, + $isNew + ] + ); + + if ($subModified) { + // An event handler told us that it modified the object. + $data = $vobj->serialize(); + + // Using md5 to figure out if there was an *actual* change. + if (!$modified && $before !== md5($data)) { + $modified = true; + } + + } + + // Destroy circular references so PHP will garbage collect the object. + $vobj->destroy(); + + } + + + /** + * This method is used to generate HTML output for the + * DAV\Browser\Plugin. This allows us to generate an interface users + * can use to create new calendars. + * + * @param DAV\INode $node + * @param string $output + * @return bool + */ + function htmlActionsPanel(DAV\INode $node, &$output) { + + if (!$node instanceof CalendarHome) + return; + + $output .= '
+

Create new calendar

+ + +
+
+ +
+ '; + + return false; + + } + + /** + * This event is triggered after GET requests. + * + * This is used to transform data into jCal, if this was requested. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function httpAfterGet(RequestInterface $request, ResponseInterface $response) { + + if (strpos($response->getHeader('Content-Type'), 'text/calendar') === false) { + return; + } + + $result = HTTP\Util::negotiate( + $request->getHeader('Accept'), + ['text/calendar', 'application/calendar+json'] + ); + + if ($result !== 'application/calendar+json') { + // Do nothing + return; + } + + // Transforming. + $vobj = VObject\Reader::read($response->getBody()); + + $jsonBody = json_encode($vobj->jsonSerialize()); + $response->setBody($jsonBody); + + // Destroy circular references so PHP will garbage collect the object. + $vobj->destroy(); + + $response->setHeader('Content-Type', 'application/calendar+json'); + $response->setHeader('Content-Length', strlen($jsonBody)); + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Adds support for CalDAV (rfc4791)', + 'link' => 'http://sabre.io/dav/caldav/', + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Principal/Collection.php b/vendor/sabre/dav/lib/CalDAV/Principal/Collection.php new file mode 100644 index 000000000..e19719a76 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Principal/Collection.php @@ -0,0 +1,33 @@ +principalBackend, $principalInfo); + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Principal/IProxyRead.php b/vendor/sabre/dav/lib/CalDAV/Principal/IProxyRead.php new file mode 100644 index 000000000..7dd375932 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Principal/IProxyRead.php @@ -0,0 +1,19 @@ +principalInfo = $principalInfo; + $this->principalBackend = $principalBackend; + + } + + /** + * Returns this principals name. + * + * @return string + */ + function getName() { + + return 'calendar-proxy-read'; + + } + + /** + * Returns the last modification time + * + * @return null + */ + function getLastModified() { + + return null; + + } + + /** + * Deletes the current node + * + * @throws DAV\Exception\Forbidden + * @return void + */ + function delete() { + + throw new DAV\Exception\Forbidden('Permission denied to delete node'); + + } + + /** + * Renames the node + * + * @throws DAV\Exception\Forbidden + * @param string $name The new name + * @return void + */ + function setName($name) { + + throw new DAV\Exception\Forbidden('Permission denied to rename file'); + + } + + + /** + * Returns a list of alternative urls for a principal + * + * This can for example be an email address, or ldap url. + * + * @return array + */ + function getAlternateUriSet() { + + return []; + + } + + /** + * Returns the full principal url + * + * @return string + */ + function getPrincipalUrl() { + + return $this->principalInfo['uri'] . '/' . $this->getName(); + + } + + /** + * Returns the list of group members + * + * If this principal is a group, this function should return + * all member principal uri's for the group. + * + * @return array + */ + function getGroupMemberSet() { + + return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl()); + + } + + /** + * Returns the list of groups this principal is member of + * + * If this principal is a member of a (list of) groups, this function + * should return a list of principal uri's for it's members. + * + * @return array + */ + function getGroupMembership() { + + return $this->principalBackend->getGroupMembership($this->getPrincipalUrl()); + + } + + /** + * Sets a list of group members + * + * If this principal is a group, this method sets all the group members. + * The list of members is always overwritten, never appended to. + * + * This method should throw an exception if the members could not be set. + * + * @param array $principals + * @return void + */ + function setGroupMemberSet(array $principals) { + + $this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals); + + } + + /** + * Returns the displayname + * + * This should be a human readable name for the principal. + * If none is available, return the nodename. + * + * @return string + */ + function getDisplayName() { + + return $this->getName(); + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php b/vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php new file mode 100644 index 000000000..8124c05e0 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php @@ -0,0 +1,181 @@ +principalInfo = $principalInfo; + $this->principalBackend = $principalBackend; + + } + + /** + * Returns this principals name. + * + * @return string + */ + function getName() { + + return 'calendar-proxy-write'; + + } + + /** + * Returns the last modification time + * + * @return null + */ + function getLastModified() { + + return null; + + } + + /** + * Deletes the current node + * + * @throws DAV\Exception\Forbidden + * @return void + */ + function delete() { + + throw new DAV\Exception\Forbidden('Permission denied to delete node'); + + } + + /** + * Renames the node + * + * @throws DAV\Exception\Forbidden + * @param string $name The new name + * @return void + */ + function setName($name) { + + throw new DAV\Exception\Forbidden('Permission denied to rename file'); + + } + + + /** + * Returns a list of alternative urls for a principal + * + * This can for example be an email address, or ldap url. + * + * @return array + */ + function getAlternateUriSet() { + + return []; + + } + + /** + * Returns the full principal url + * + * @return string + */ + function getPrincipalUrl() { + + return $this->principalInfo['uri'] . '/' . $this->getName(); + + } + + /** + * Returns the list of group members + * + * If this principal is a group, this function should return + * all member principal uri's for the group. + * + * @return array + */ + function getGroupMemberSet() { + + return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl()); + + } + + /** + * Returns the list of groups this principal is member of + * + * If this principal is a member of a (list of) groups, this function + * should return a list of principal uri's for it's members. + * + * @return array + */ + function getGroupMembership() { + + return $this->principalBackend->getGroupMembership($this->getPrincipalUrl()); + + } + + /** + * Sets a list of group members + * + * If this principal is a group, this method sets all the group members. + * The list of members is always overwritten, never appended to. + * + * This method should throw an exception if the members could not be set. + * + * @param array $principals + * @return void + */ + function setGroupMemberSet(array $principals) { + + $this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals); + + } + + /** + * Returns the displayname + * + * This should be a human readable name for the principal. + * If none is available, return the nodename. + * + * @return string + */ + function getDisplayName() { + + return $this->getName(); + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Principal/User.php b/vendor/sabre/dav/lib/CalDAV/Principal/User.php new file mode 100644 index 000000000..6e97e7cca --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Principal/User.php @@ -0,0 +1,135 @@ +principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/' . $name); + if (!$principal) { + throw new DAV\Exception\NotFound('Node with name ' . $name . ' was not found'); + } + if ($name === 'calendar-proxy-read') + return new ProxyRead($this->principalBackend, $this->principalProperties); + + if ($name === 'calendar-proxy-write') + return new ProxyWrite($this->principalBackend, $this->principalProperties); + + throw new DAV\Exception\NotFound('Node with name ' . $name . ' was not found'); + + } + + /** + * Returns an array with all the child nodes + * + * @return DAV\INode[] + */ + function getChildren() { + + $r = []; + if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-read')) { + $r[] = new ProxyRead($this->principalBackend, $this->principalProperties); + } + if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-write')) { + $r[] = new ProxyWrite($this->principalBackend, $this->principalProperties); + } + + return $r; + + } + + /** + * Returns whether or not the child node exists + * + * @param string $name + * @return bool + */ + function childExists($name) { + + try { + $this->getChild($name); + return true; + } catch (DAV\Exception\NotFound $e) { + return false; + } + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + $acl = parent::getACL(); + $acl[] = [ + 'privilege' => '{DAV:}read', + 'principal' => $this->principalProperties['uri'] . '/calendar-proxy-read', + 'protected' => true, + ]; + $acl[] = [ + 'privilege' => '{DAV:}read', + 'principal' => $this->principalProperties['uri'] . '/calendar-proxy-write', + 'protected' => true, + ]; + return $acl; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php b/vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php new file mode 100644 index 000000000..c9fd77d93 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php @@ -0,0 +1,15 @@ +senderEmail = $senderEmail; + + } + + /* + * This initializes the plugin. + * + * This function is called by Sabre\DAV\Server, after + * addPlugin is called. + * + * This method should set up the required event subscriptions. + * + * @param DAV\Server $server + * @return void + */ + function initialize(DAV\Server $server) { + + $server->on('schedule', [$this, 'schedule'], 120); + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using \Sabre\DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'imip'; + + } + + /** + * Event handler for the 'schedule' event. + * + * @param ITip\Message $iTipMessage + * @return void + */ + function schedule(ITip\Message $iTipMessage) { + + // Not sending any emails if the system considers the update + // insignificant. + if (!$iTipMessage->significantChange) { + if (!$iTipMessage->scheduleStatus) { + $iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email'; + } + return; + } + + $summary = $iTipMessage->message->VEVENT->SUMMARY; + + if (parse_url($iTipMessage->sender, PHP_URL_SCHEME) !== 'mailto') + return; + + if (parse_url($iTipMessage->recipient, PHP_URL_SCHEME) !== 'mailto') + return; + + $sender = substr($iTipMessage->sender, 7); + $recipient = substr($iTipMessage->recipient, 7); + + if ($iTipMessage->senderName) { + $sender = $iTipMessage->senderName . ' <' . $sender . '>'; + } + if ($iTipMessage->recipientName) { + $recipient = $iTipMessage->recipientName . ' <' . $recipient . '>'; + } + + $subject = 'SabreDAV iTIP message'; + switch (strtoupper($iTipMessage->method)) { + case 'REPLY' : + $subject = 'Re: ' . $summary; + break; + case 'REQUEST' : + $subject = $summary; + break; + case 'CANCEL' : + $subject = 'Cancelled: ' . $summary; + break; + } + + $headers = [ + 'Reply-To: ' . $sender, + 'From: ' . $this->senderEmail, + 'Content-Type: text/calendar; charset=UTF-8; method=' . $iTipMessage->method, + ]; + if (DAV\Server::$exposeVersion) { + $headers[] = 'X-Sabre-Version: ' . DAV\Version::VERSION; + } + $this->mail( + $recipient, + $subject, + $iTipMessage->message->serialize(), + $headers + ); + $iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip'; + + } + + // @codeCoverageIgnoreStart + // This is deemed untestable in a reasonable manner + + /** + * This function is responsible for sending the actual email. + * + * @param string $to Recipient email address + * @param string $subject Subject of the email + * @param string $body iCalendar body + * @param array $headers List of headers + * @return void + */ + protected function mail($to, $subject, $body, array $headers) { + + mail($to, $subject, $body, implode("\r\n", $headers)); + + } + + // @codeCoverageIgnoreEnd + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Email delivery (rfc6037) for CalDAV scheduling', + 'link' => 'http://sabre.io/dav/scheduling/', + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php b/vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php new file mode 100644 index 000000000..88fbdc411 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php @@ -0,0 +1,15 @@ +caldavBackend = $caldavBackend; + $this->principalUri = $principalUri; + + } + + /** + * Returns the name of the node. + * + * This is used to generate the url. + * + * @return string + */ + function getName() { + + return 'inbox'; + + } + + /** + * Returns an array with all the child nodes + * + * @return \Sabre\DAV\INode[] + */ + function getChildren() { + + $objs = $this->caldavBackend->getSchedulingObjects($this->principalUri); + $children = []; + foreach ($objs as $obj) { + //$obj['acl'] = $this->getACL(); + $obj['principaluri'] = $this->principalUri; + $children[] = new SchedulingObject($this->caldavBackend, $obj); + } + return $children; + + } + + /** + * Creates a new file in the directory + * + * Data will either be supplied as a stream resource, or in certain cases + * as a string. Keep in mind that you may have to support either. + * + * After succesful creation of the file, you may choose to return the ETag + * of the new file here. + * + * The returned ETag must be surrounded by double-quotes (The quotes should + * be part of the actual string). + * + * If you cannot accurately determine the ETag, you should not return it. + * If you don't store the file exactly as-is (you're transforming it + * somehow) you should also not return an ETag. + * + * This means that if a subsequent GET to this new file does not exactly + * return the same contents of what was submitted here, you are strongly + * recommended to omit the ETag. + * + * @param string $name Name of the file + * @param resource|string $data Initial payload + * @return null|string + */ + function createFile($name, $data = null) { + + $this->caldavBackend->createSchedulingObject($this->principalUri, $name, $data); + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->principalUri; + + } + + /** + * Returns a group principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getGroup() { + + return null; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + return [ + [ + 'privilege' => '{DAV:}read', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write-properties', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}unbind', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}unbind', + 'principal' => $this->getOwner() . '/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-deliver-invite', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ], + [ + 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-deliver-reply', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ], + ]; + + } + + /** + * Updates the ACL + * + * This method will receive a list of new ACE's. + * + * @param array $acl + * @return void + */ + function setACL(array $acl) { + + throw new DAV\Exception\MethodNotAllowed('You\'re not allowed to update the ACL'); + + } + + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + function getSupportedPrivilegeSet() { + + $ns = '{' . CalDAV\Plugin::NS_CALDAV . '}'; + + $default = DAVACL\Plugin::getDefaultSupportedPrivilegeSet(); + $default['aggregates'][] = [ + 'privilege' => $ns . 'schedule-deliver', + 'aggregates' => [ + ['privilege' => $ns . 'schedule-deliver-invite'], + ['privilege' => $ns . 'schedule-deliver-reply'], + ], + ]; + return $default; + + } + + /** + * Performs a calendar-query on the contents of this calendar. + * + * The calendar-query is defined in RFC4791 : CalDAV. Using the + * calendar-query it is possible for a client to request a specific set of + * object, based on contents of iCalendar properties, date-ranges and + * iCalendar component types (VTODO, VEVENT). + * + * This method should just return a list of (relative) urls that match this + * query. + * + * The list of filters are specified as an array. The exact array is + * documented by \Sabre\CalDAV\CalendarQueryParser. + * + * @param array $filters + * @return array + */ + function calendarQuery(array $filters) { + + $result = []; + $validator = new CalDAV\CalendarQueryValidator(); + + $objects = $this->caldavBackend->getSchedulingObjects($this->principalUri); + foreach ($objects as $object) { + $vObject = VObject\Reader::read($object['calendardata']); + if ($validator->validate($vObject, $filters)) { + $result[] = $object['uri']; + } + + // Destroy circular references to PHP will GC the object. + $vObject->destroy(); + } + return $result; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php b/vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php new file mode 100644 index 000000000..dabaee2ca --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php @@ -0,0 +1,184 @@ +principalUri = $principalUri; + + } + + /** + * Returns the name of the node. + * + * This is used to generate the url. + * + * @return string + */ + function getName() { + + return 'outbox'; + + } + + /** + * Returns an array with all the child nodes + * + * @return \Sabre\DAV\INode[] + */ + function getChildren() { + + return []; + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->principalUri; + + } + + /** + * Returns a group principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getGroup() { + + return null; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + return [ + [ + 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + [ + 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + [ + 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy', + 'principal' => $this->getOwner() . '/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent', + 'principal' => $this->getOwner() . '/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner() . '/calendar-proxy-read', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner() . '/calendar-proxy-write', + 'protected' => true, + ], + ]; + + } + + /** + * Updates the ACL + * + * This method will receive a list of new ACE's. + * + * @param array $acl + * @return void + */ + function setACL(array $acl) { + + throw new DAV\Exception\MethodNotAllowed('You\'re not allowed to update the ACL'); + + } + + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + function getSupportedPrivilegeSet() { + + $default = DAVACL\Plugin::getDefaultSupportedPrivilegeSet(); + $default['aggregates'][] = [ + 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy', + ]; + $default['aggregates'][] = [ + 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent', + ]; + + return $default; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php b/vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php new file mode 100644 index 000000000..827d6209b --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php @@ -0,0 +1,994 @@ +server = $server; + $server->on('method:POST', [$this, 'httpPost']); + $server->on('propFind', [$this, 'propFind']); + $server->on('propPatch', [$this, 'propPatch']); + $server->on('calendarObjectChange', [$this, 'calendarObjectChange']); + $server->on('beforeUnbind', [$this, 'beforeUnbind']); + $server->on('schedule', [$this, 'scheduleLocalDelivery']); + + $ns = '{' . self::NS_CALDAV . '}'; + + /** + * This information ensures that the {DAV:}resourcetype property has + * the correct values. + */ + $server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IOutbox'] = $ns . 'schedule-outbox'; + $server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IInbox'] = $ns . 'schedule-inbox'; + + /** + * Properties we protect are made read-only by the server. + */ + array_push($server->protectedProperties, + $ns . 'schedule-inbox-URL', + $ns . 'schedule-outbox-URL', + $ns . 'calendar-user-address-set', + $ns . 'calendar-user-type', + $ns . 'schedule-default-calendar-URL' + ); + + } + + /** + * Use this method to tell the server this plugin defines additional + * HTTP methods. + * + * This method is passed a uri. It should only return HTTP methods that are + * available for the specified uri. + * + * @param string $uri + * @return array + */ + function getHTTPMethods($uri) { + + try { + $node = $this->server->tree->getNodeForPath($uri); + } catch (NotFound $e) { + return []; + } + + if ($node instanceof IOutbox) { + return ['POST']; + } + + return []; + + } + + /** + * This method handles POST request for the outbox. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpPost(RequestInterface $request, ResponseInterface $response) { + + // Checking if this is a text/calendar content type + $contentType = $request->getHeader('Content-Type'); + if (strpos($contentType, 'text/calendar') !== 0) { + return; + } + + $path = $request->getPath(); + + // Checking if we're talking to an outbox + try { + $node = $this->server->tree->getNodeForPath($path); + } catch (NotFound $e) { + return; + } + if (!$node instanceof IOutbox) + return; + + $this->server->transactionType = 'post-caldav-outbox'; + $this->outboxRequest($node, $request, $response); + + // Returning false breaks the event chain and tells the server we've + // handled the request. + return false; + + } + + /** + * This method handler is invoked during fetching of properties. + * + * We use this event to add calendar-auto-schedule-specific properties. + * + * @param PropFind $propFind + * @param INode $node + * @return void + */ + function propFind(PropFind $propFind, INode $node) { + + if ($node instanceof DAVACL\IPrincipal) { + + $caldavPlugin = $this->server->getPlugin('caldav'); + $principalUrl = $node->getPrincipalUrl(); + + // schedule-outbox-URL property + $propFind->handle('{' . self::NS_CALDAV . '}schedule-outbox-URL', function() use ($principalUrl, $caldavPlugin) { + + $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl); + if (!$calendarHomePath) { + return null; + } + $outboxPath = $calendarHomePath . '/outbox/'; + + return new Href($outboxPath); + + }); + // schedule-inbox-URL property + $propFind->handle('{' . self::NS_CALDAV . '}schedule-inbox-URL', function() use ($principalUrl, $caldavPlugin) { + + $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl); + if (!$calendarHomePath) { + return null; + } + $inboxPath = $calendarHomePath . '/inbox/'; + + return new Href($inboxPath); + + }); + + $propFind->handle('{' . self::NS_CALDAV . '}schedule-default-calendar-URL', function() use ($principalUrl, $caldavPlugin) { + + // We don't support customizing this property yet, so in the + // meantime we just grab the first calendar in the home-set. + $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl); + + if (!$calendarHomePath) { + return null; + } + + $sccs = '{' . self::NS_CALDAV . '}supported-calendar-component-set'; + + $result = $this->server->getPropertiesForPath($calendarHomePath, [ + '{DAV:}resourcetype', + $sccs, + ], 1); + + foreach ($result as $child) { + if (!isset($child[200]['{DAV:}resourcetype']) || !$child[200]['{DAV:}resourcetype']->is('{' . self::NS_CALDAV . '}calendar') || $child[200]['{DAV:}resourcetype']->is('{http://calendarserver.org/ns/}shared')) { + // Node is either not a calendar or a shared instance. + continue; + } + if (!isset($child[200][$sccs]) || in_array('VEVENT', $child[200][$sccs]->getValue())) { + // Either there is no supported-calendar-component-set + // (which is fine) or we found one that supports VEVENT. + return new Href($child['href']); + } + } + + }); + + // The server currently reports every principal to be of type + // 'INDIVIDUAL' + $propFind->handle('{' . self::NS_CALDAV . '}calendar-user-type', function() { + + return 'INDIVIDUAL'; + + }); + + } + + // Mapping the old property to the new property. + $propFind->handle('{http://calendarserver.org/ns/}calendar-availability', function() use ($propFind, $node) { + + // In case it wasn't clear, the only difference is that we map the + // old property to a different namespace. + $availProp = '{' . self::NS_CALDAV . '}calendar-availability'; + $subPropFind = new PropFind( + $propFind->getPath(), + [$availProp] + ); + + $this->server->getPropertiesByNode( + $subPropFind, + $node + ); + + $propFind->set( + '{http://calendarserver.org/ns/}calendar-availability', + $subPropFind->get($availProp), + $subPropFind->getStatus($availProp) + ); + + }); + + } + + /** + * This method is called during property updates. + * + * @param string $path + * @param PropPatch $propPatch + * @return void + */ + function propPatch($path, PropPatch $propPatch) { + + // Mapping the old property to the new property. + $propPatch->handle('{http://calendarserver.org/ns/}calendar-availability', function($value) use ($path) { + + $availProp = '{' . self::NS_CALDAV . '}calendar-availability'; + $subPropPatch = new PropPatch([$availProp => $value]); + $this->server->emit('propPatch', [$path, $subPropPatch]); + $subPropPatch->commit(); + + return $subPropPatch->getResult()[$availProp]; + + }); + + } + + /** + * This method is triggered whenever there was a calendar object gets + * created or updated. + * + * @param RequestInterface $request HTTP request + * @param ResponseInterface $response HTTP Response + * @param VCalendar $vCal Parsed iCalendar object + * @param mixed $calendarPath Path to calendar collection + * @param mixed $modified The iCalendar object has been touched. + * @param mixed $isNew Whether this was a new item or we're updating one + * @return void + */ + function calendarObjectChange(RequestInterface $request, ResponseInterface $response, VCalendar $vCal, $calendarPath, &$modified, $isNew) { + + if (!$this->scheduleReply($this->server->httpRequest)) { + return; + } + + $calendarNode = $this->server->tree->getNodeForPath($calendarPath); + + $addresses = $this->getAddressesForPrincipal( + $calendarNode->getOwner() + ); + + if (!$isNew) { + $node = $this->server->tree->getNodeForPath($request->getPath()); + $oldObj = Reader::read($node->get()); + } else { + $oldObj = null; + } + + $this->processICalendarChange($oldObj, $vCal, $addresses, [], $modified); + + if ($oldObj) { + // Destroy circular references so PHP will GC the object. + $oldObj->destroy(); + } + + } + + /** + * This method is responsible for delivering the ITip message. + * + * @param ITip\Message $itipMessage + * @return void + */ + function deliver(ITip\Message $iTipMessage) { + + $this->server->emit('schedule', [$iTipMessage]); + if (!$iTipMessage->scheduleStatus) { + $iTipMessage->scheduleStatus = '5.2;There was no system capable of delivering the scheduling message'; + } + // In case the change was considered 'insignificant', we are going to + // remove any error statuses, if any. See ticket #525. + list($baseCode) = explode('.', $iTipMessage->scheduleStatus); + if (!$iTipMessage->significantChange && in_array($baseCode, ['3', '5'])) { + $iTipMessage->scheduleStatus = null; + } + + } + + /** + * This method is triggered before a file gets deleted. + * + * We use this event to make sure that when this happens, attendees get + * cancellations, and organizers get 'DECLINED' statuses. + * + * @param string $path + * @return void + */ + function beforeUnbind($path) { + + // FIXME: We shouldn't trigger this functionality when we're issuing a + // MOVE. This is a hack. + if ($this->server->httpRequest->getMethod() === 'MOVE') return; + + $node = $this->server->tree->getNodeForPath($path); + + if (!$node instanceof ICalendarObject || $node instanceof ISchedulingObject) { + return; + } + + if (!$this->scheduleReply($this->server->httpRequest)) { + return; + } + + $addresses = $this->getAddressesForPrincipal( + $node->getOwner() + ); + + $broker = new ITip\Broker(); + $messages = $broker->parseEvent(null, $addresses, $node->get()); + + foreach ($messages as $message) { + $this->deliver($message); + } + + } + + /** + * Event handler for the 'schedule' event. + * + * This handler attempts to look at local accounts to deliver the + * scheduling object. + * + * @param ITip\Message $iTipMessage + * @return void + */ + function scheduleLocalDelivery(ITip\Message $iTipMessage) { + + $aclPlugin = $this->server->getPlugin('acl'); + + // Local delivery is not available if the ACL plugin is not loaded. + if (!$aclPlugin) { + return; + } + + $caldavNS = '{' . self::NS_CALDAV . '}'; + + $principalUri = $aclPlugin->getPrincipalByUri($iTipMessage->recipient); + if (!$principalUri) { + $iTipMessage->scheduleStatus = '3.7;Could not find principal.'; + return; + } + + // We found a principal URL, now we need to find its inbox. + // Unfortunately we may not have sufficient privileges to find this, so + // we are temporarily turning off ACL to let this come through. + // + // Once we support PHP 5.5, this should be wrapped in a try..finally + // block so we can ensure that this privilege gets added again after. + $this->server->removeListener('propFind', [$aclPlugin, 'propFind']); + + $result = $this->server->getProperties( + $principalUri, + [ + '{DAV:}principal-URL', + $caldavNS . 'calendar-home-set', + $caldavNS . 'schedule-inbox-URL', + $caldavNS . 'schedule-default-calendar-URL', + '{http://sabredav.org/ns}email-address', + ] + ); + + // Re-registering the ACL event + $this->server->on('propFind', [$aclPlugin, 'propFind'], 20); + + if (!isset($result[$caldavNS . 'schedule-inbox-URL'])) { + $iTipMessage->scheduleStatus = '5.2;Could not find local inbox'; + return; + } + if (!isset($result[$caldavNS . 'calendar-home-set'])) { + $iTipMessage->scheduleStatus = '5.2;Could not locate a calendar-home-set'; + return; + } + if (!isset($result[$caldavNS . 'schedule-default-calendar-URL'])) { + $iTipMessage->scheduleStatus = '5.2;Could not find a schedule-default-calendar-URL property'; + return; + } + + $calendarPath = $result[$caldavNS . 'schedule-default-calendar-URL']->getHref(); + $homePath = $result[$caldavNS . 'calendar-home-set']->getHref(); + $inboxPath = $result[$caldavNS . 'schedule-inbox-URL']->getHref(); + + if ($iTipMessage->method === 'REPLY') { + $privilege = 'schedule-deliver-reply'; + } else { + $privilege = 'schedule-deliver-invite'; + } + + if (!$aclPlugin->checkPrivileges($inboxPath, $caldavNS . $privilege, DAVACL\Plugin::R_PARENT, false)) { + $iTipMessage->scheduleStatus = '3.8;organizer did not have the ' . $privilege . ' privilege on the attendees inbox'; + return; + } + + // Next, we're going to find out if the item already exits in one of + // the users' calendars. + $uid = $iTipMessage->uid; + + $newFileName = 'sabredav-' . \Sabre\DAV\UUIDUtil::getUUID() . '.ics'; + + $home = $this->server->tree->getNodeForPath($homePath); + $inbox = $this->server->tree->getNodeForPath($inboxPath); + + $currentObject = null; + $objectNode = null; + $isNewNode = false; + + $result = $home->getCalendarObjectByUID($uid); + if ($result) { + // There was an existing object, we need to update probably. + $objectPath = $homePath . '/' . $result; + $objectNode = $this->server->tree->getNodeForPath($objectPath); + $oldICalendarData = $objectNode->get(); + $currentObject = Reader::read($oldICalendarData); + } else { + $isNewNode = true; + } + + $broker = new ITip\Broker(); + $newObject = $broker->processMessage($iTipMessage, $currentObject); + + $inbox->createFile($newFileName, $iTipMessage->message->serialize()); + + if (!$newObject) { + // We received an iTip message referring to a UID that we don't + // have in any calendars yet, and processMessage did not give us a + // calendarobject back. + // + // The implication is that processMessage did not understand the + // iTip message. + $iTipMessage->scheduleStatus = '5.0;iTip message was not processed by the server, likely because we didn\'t understand it.'; + return; + } + + // Note that we are bypassing ACL on purpose by calling this directly. + // We may need to look a bit deeper into this later. Supporting ACL + // here would be nice. + if ($isNewNode) { + $calendar = $this->server->tree->getNodeForPath($calendarPath); + $calendar->createFile($newFileName, $newObject->serialize()); + } else { + // If the message was a reply, we may have to inform other + // attendees of this attendees status. Therefore we're shooting off + // another itipMessage. + if ($iTipMessage->method === 'REPLY') { + $this->processICalendarChange( + $oldICalendarData, + $newObject, + [$iTipMessage->recipient], + [$iTipMessage->sender] + ); + } + $objectNode->put($newObject->serialize()); + } + $iTipMessage->scheduleStatus = '1.2;Message delivered locally'; + + } + + /** + * This method looks at an old iCalendar object, a new iCalendar object and + * starts sending scheduling messages based on the changes. + * + * A list of addresses needs to be specified, so the system knows who made + * the update, because the behavior may be different based on if it's an + * attendee or an organizer. + * + * This method may update $newObject to add any status changes. + * + * @param VCalendar|string $oldObject + * @param VCalendar $newObject + * @param array $addresses + * @param array $ignore Any addresses to not send messages to. + * @param bool $modified A marker to indicate that the original object + * modified by this process. + * @return void + */ + protected function processICalendarChange($oldObject = null, VCalendar $newObject, array $addresses, array $ignore = [], &$modified = false) { + + $broker = new ITip\Broker(); + $messages = $broker->parseEvent($newObject, $addresses, $oldObject); + + if ($messages) $modified = true; + + foreach ($messages as $message) { + + if (in_array($message->recipient, $ignore)) { + continue; + } + + $this->deliver($message); + + if (isset($newObject->VEVENT->ORGANIZER) && ($newObject->VEVENT->ORGANIZER->getNormalizedValue() === $message->recipient)) { + if ($message->scheduleStatus) { + $newObject->VEVENT->ORGANIZER['SCHEDULE-STATUS'] = $message->getScheduleStatus(); + } + unset($newObject->VEVENT->ORGANIZER['SCHEDULE-FORCE-SEND']); + + } else { + + if (isset($newObject->VEVENT->ATTENDEE)) foreach ($newObject->VEVENT->ATTENDEE as $attendee) { + + if ($attendee->getNormalizedValue() === $message->recipient) { + if ($message->scheduleStatus) { + $attendee['SCHEDULE-STATUS'] = $message->getScheduleStatus(); + } + unset($attendee['SCHEDULE-FORCE-SEND']); + break; + } + + } + + } + + } + + } + + /** + * Returns a list of addresses that are associated with a principal. + * + * @param string $principal + * @return array + */ + protected function getAddressesForPrincipal($principal) { + + $CUAS = '{' . self::NS_CALDAV . '}calendar-user-address-set'; + + $properties = $this->server->getProperties( + $principal, + [$CUAS] + ); + + // If we can't find this information, we'll stop processing + if (!isset($properties[$CUAS])) { + return; + } + + $addresses = $properties[$CUAS]->getHrefs(); + return $addresses; + + } + + /** + * This method handles POST requests to the schedule-outbox. + * + * Currently, two types of requests are support: + * * FREEBUSY requests from RFC 6638 + * * Simple iTIP messages from draft-desruisseaux-caldav-sched-04 + * + * The latter is from an expired early draft of the CalDAV scheduling + * extensions, but iCal depends on a feature from that spec, so we + * implement it. + * + * @param IOutbox $outboxNode + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function outboxRequest(IOutbox $outboxNode, RequestInterface $request, ResponseInterface $response) { + + $outboxPath = $request->getPath(); + + // Parsing the request body + try { + $vObject = VObject\Reader::read($request->getBody()); + } catch (VObject\ParseException $e) { + throw new BadRequest('The request body must be a valid iCalendar object. Parse error: ' . $e->getMessage()); + } + + // The incoming iCalendar object must have a METHOD property, and a + // component. The combination of both determines what type of request + // this is. + $componentType = null; + foreach ($vObject->getComponents() as $component) { + if ($component->name !== 'VTIMEZONE') { + $componentType = $component->name; + break; + } + } + if (is_null($componentType)) { + throw new BadRequest('We expected at least one VTODO, VJOURNAL, VFREEBUSY or VEVENT component'); + } + + // Validating the METHOD + $method = strtoupper((string)$vObject->METHOD); + if (!$method) { + throw new BadRequest('A METHOD property must be specified in iTIP messages'); + } + + // So we support one type of request: + // + // REQUEST with a VFREEBUSY component + + $acl = $this->server->getPlugin('acl'); + + if ($componentType === 'VFREEBUSY' && $method === 'REQUEST') { + + $acl && $acl->checkPrivileges($outboxPath, '{' . self::NS_CALDAV . '}schedule-query-freebusy'); + $this->handleFreeBusyRequest($outboxNode, $vObject, $request, $response); + + // Destroy circular references so PHP can GC the object. + $vObject->destroy(); + unset($vObject); + + } else { + + throw new NotImplemented('We only support VFREEBUSY (REQUEST) on this endpoint'); + + } + + } + + /** + * This method is responsible for parsing a free-busy query request and + * returning it's result. + * + * @param IOutbox $outbox + * @param VObject\Component $vObject + * @param RequestInterface $request + * @param ResponseInterface $response + * @return string + */ + protected function handleFreeBusyRequest(IOutbox $outbox, VObject\Component $vObject, RequestInterface $request, ResponseInterface $response) { + + $vFreeBusy = $vObject->VFREEBUSY; + $organizer = $vFreeBusy->organizer; + + $organizer = (string)$organizer; + + // Validating if the organizer matches the owner of the inbox. + $owner = $outbox->getOwner(); + + $caldavNS = '{' . self::NS_CALDAV . '}'; + + $uas = $caldavNS . 'calendar-user-address-set'; + $props = $this->server->getProperties($owner, [$uas]); + + if (empty($props[$uas]) || !in_array($organizer, $props[$uas]->getHrefs())) { + throw new Forbidden('The organizer in the request did not match any of the addresses for the owner of this inbox'); + } + + if (!isset($vFreeBusy->ATTENDEE)) { + throw new BadRequest('You must at least specify 1 attendee'); + } + + $attendees = []; + foreach ($vFreeBusy->ATTENDEE as $attendee) { + $attendees[] = (string)$attendee; + } + + + if (!isset($vFreeBusy->DTSTART) || !isset($vFreeBusy->DTEND)) { + throw new BadRequest('DTSTART and DTEND must both be specified'); + } + + $startRange = $vFreeBusy->DTSTART->getDateTime(); + $endRange = $vFreeBusy->DTEND->getDateTime(); + + $results = []; + foreach ($attendees as $attendee) { + $results[] = $this->getFreeBusyForEmail($attendee, $startRange, $endRange, $vObject); + } + + $dom = new \DOMDocument('1.0', 'utf-8'); + $dom->formatOutput = true; + $scheduleResponse = $dom->createElement('cal:schedule-response'); + foreach ($this->server->xml->namespaceMap as $namespace => $prefix) { + + $scheduleResponse->setAttribute('xmlns:' . $prefix, $namespace); + + } + $dom->appendChild($scheduleResponse); + + foreach ($results as $result) { + $xresponse = $dom->createElement('cal:response'); + + $recipient = $dom->createElement('cal:recipient'); + $recipientHref = $dom->createElement('d:href'); + + $recipientHref->appendChild($dom->createTextNode($result['href'])); + $recipient->appendChild($recipientHref); + $xresponse->appendChild($recipient); + + $reqStatus = $dom->createElement('cal:request-status'); + $reqStatus->appendChild($dom->createTextNode($result['request-status'])); + $xresponse->appendChild($reqStatus); + + if (isset($result['calendar-data'])) { + + $calendardata = $dom->createElement('cal:calendar-data'); + $calendardata->appendChild($dom->createTextNode(str_replace("\r\n", "\n", $result['calendar-data']->serialize()))); + $xresponse->appendChild($calendardata); + + } + $scheduleResponse->appendChild($xresponse); + } + + $response->setStatus(200); + $response->setHeader('Content-Type', 'application/xml'); + $response->setBody($dom->saveXML()); + + } + + /** + * Returns free-busy information for a specific address. The returned + * data is an array containing the following properties: + * + * calendar-data : A VFREEBUSY VObject + * request-status : an iTip status code. + * href: The principal's email address, as requested + * + * The following request status codes may be returned: + * * 2.0;description + * * 3.7;description + * + * @param string $email address + * @param DateTimeInterface $start + * @param DateTimeInterface $end + * @param VObject\Component $request + * @return array + */ + protected function getFreeBusyForEmail($email, \DateTimeInterface $start, \DateTimeInterface $end, VObject\Component $request) { + + $caldavNS = '{' . self::NS_CALDAV . '}'; + + $aclPlugin = $this->server->getPlugin('acl'); + if (substr($email, 0, 7) === 'mailto:') $email = substr($email, 7); + + $result = $aclPlugin->principalSearch( + ['{http://sabredav.org/ns}email-address' => $email], + [ + '{DAV:}principal-URL', + $caldavNS . 'calendar-home-set', + $caldavNS . 'schedule-inbox-URL', + '{http://sabredav.org/ns}email-address', + + ] + ); + + if (!count($result)) { + return [ + 'request-status' => '3.7;Could not find principal', + 'href' => 'mailto:' . $email, + ]; + } + + if (!isset($result[0][200][$caldavNS . 'calendar-home-set'])) { + return [ + 'request-status' => '3.7;No calendar-home-set property found', + 'href' => 'mailto:' . $email, + ]; + } + if (!isset($result[0][200][$caldavNS . 'schedule-inbox-URL'])) { + return [ + 'request-status' => '3.7;No schedule-inbox-URL property found', + 'href' => 'mailto:' . $email, + ]; + } + $homeSet = $result[0][200][$caldavNS . 'calendar-home-set']->getHref(); + $inboxUrl = $result[0][200][$caldavNS . 'schedule-inbox-URL']->getHref(); + + // Grabbing the calendar list + $objects = []; + $calendarTimeZone = new DateTimeZone('UTC'); + + foreach ($this->server->tree->getNodeForPath($homeSet)->getChildren() as $node) { + if (!$node instanceof ICalendar) { + continue; + } + + $sct = $caldavNS . 'schedule-calendar-transp'; + $ctz = $caldavNS . 'calendar-timezone'; + $props = $node->getProperties([$sct, $ctz]); + + if (isset($props[$sct]) && $props[$sct]->getValue() == ScheduleCalendarTransp::TRANSPARENT) { + // If a calendar is marked as 'transparent', it means we must + // ignore it for free-busy purposes. + continue; + } + + $aclPlugin->checkPrivileges($homeSet . $node->getName(), $caldavNS . 'read-free-busy'); + + if (isset($props[$ctz])) { + $vtimezoneObj = VObject\Reader::read($props[$ctz]); + $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone(); + + // Destroy circular references so PHP can garbage collect the object. + $vtimezoneObj->destroy(); + + } + + // Getting the list of object uris within the time-range + $urls = $node->calendarQuery([ + 'name' => 'VCALENDAR', + 'comp-filters' => [ + [ + 'name' => 'VEVENT', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => [ + 'start' => $start, + 'end' => $end, + ], + ], + ], + 'prop-filters' => [], + 'is-not-defined' => false, + 'time-range' => null, + ]); + + $calObjects = array_map(function($url) use ($node) { + $obj = $node->getChild($url)->get(); + return $obj; + }, $urls); + + $objects = array_merge($objects, $calObjects); + + } + + $inboxProps = $this->server->getProperties( + $inboxUrl, + $caldavNS . 'calendar-availability' + ); + + $vcalendar = new VObject\Component\VCalendar(); + $vcalendar->METHOD = 'REPLY'; + + $generator = new VObject\FreeBusyGenerator(); + $generator->setObjects($objects); + $generator->setTimeRange($start, $end); + $generator->setBaseObject($vcalendar); + $generator->setTimeZone($calendarTimeZone); + + if ($inboxProps) { + $generator->setVAvailability( + VObject\Reader::read( + $inboxProps[$caldavNS . 'calendar-availability'] + ) + ); + } + + $result = $generator->getResult(); + + $vcalendar->VFREEBUSY->ATTENDEE = 'mailto:' . $email; + $vcalendar->VFREEBUSY->UID = (string)$request->VFREEBUSY->UID; + $vcalendar->VFREEBUSY->ORGANIZER = clone $request->VFREEBUSY->ORGANIZER; + + return [ + 'calendar-data' => $result, + 'request-status' => '2.0;Success', + 'href' => 'mailto:' . $email, + ]; + } + + /** + * This method checks the 'Schedule-Reply' header + * and returns false if it's 'F', otherwise true. + * + * @param RequestInterface $request + * @return bool + */ + private function scheduleReply(RequestInterface $request) { + + $scheduleReply = $request->getHeader('Schedule-Reply'); + return $scheduleReply !== 'F'; + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Adds calendar-auto-schedule, as defined in rf6868', + 'link' => 'http://sabre.io/dav/scheduling/', + ]; + + } +} diff --git a/vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php b/vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php new file mode 100644 index 000000000..a36646e6c --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php @@ -0,0 +1,165 @@ +caldavBackend = $caldavBackend; + + if (!isset($objectData['uri'])) { + throw new \InvalidArgumentException('The objectData argument must contain an \'uri\' property'); + } + + $this->objectData = $objectData; + + } + + /** + * Returns the ICalendar-formatted object + * + * @return string + */ + function get() { + + // Pre-populating the 'calendardata' is optional, if we don't have it + // already we fetch it from the backend. + if (!isset($this->objectData['calendardata'])) { + $this->objectData = $this->caldavBackend->getSchedulingObject($this->objectData['principaluri'], $this->objectData['uri']); + } + return $this->objectData['calendardata']; + + } + + /** + * Updates the ICalendar-formatted object + * + * @param string|resource $calendarData + * @return string + */ + function put($calendarData) { + + throw new MethodNotAllowed('Updating scheduling objects is not supported'); + + } + + /** + * Deletes the scheduling message + * + * @return void + */ + function delete() { + + $this->caldavBackend->deleteSchedulingObject($this->objectData['principaluri'], $this->objectData['uri']); + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->objectData['principaluri']; + + } + + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + // An alternative acl may be specified in the object data. + // + + if (isset($this->objectData['acl'])) { + return $this->objectData['acl']; + } + + // The default ACL + return [ + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->objectData['principaluri'], + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => $this->objectData['principaluri'], + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->objectData['principaluri'] . '/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => $this->objectData['principaluri'] . '/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->objectData['principaluri'] . '/calendar-proxy-read', + 'protected' => true, + ], + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/ShareableCalendar.php b/vendor/sabre/dav/lib/CalDAV/ShareableCalendar.php new file mode 100644 index 000000000..c11695d5f --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/ShareableCalendar.php @@ -0,0 +1,72 @@ +caldavBackend->updateShares($this->calendarInfo['id'], $add, $remove); + + } + + /** + * Returns the list of people whom this calendar is shared with. + * + * Every element in this array should have the following properties: + * * href - Often a mailto: address + * * commonName - Optional, for example a first + last name + * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants. + * * readOnly - boolean + * * summary - Optional, a description for the share + * + * @return array + */ + function getShares() { + + return $this->caldavBackend->getShares($this->calendarInfo['id']); + + } + + /** + * Marks this calendar as published. + * + * Publishing a calendar should automatically create a read-only, public, + * subscribable calendar. + * + * @param bool $value + * @return void + */ + function setPublishStatus($value) { + + $this->caldavBackend->setPublishStatus($this->calendarInfo['id'], $value); + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/SharedCalendar.php b/vendor/sabre/dav/lib/CalDAV/SharedCalendar.php new file mode 100644 index 000000000..7973eff9c --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/SharedCalendar.php @@ -0,0 +1,148 @@ +calendarInfo['{http://calendarserver.org/ns/}shared-url']; + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->calendarInfo['{http://sabredav.org/ns}owner-principal']; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + // The top-level ACL only contains access information for the true + // owner of the calendar, so we need to add the information for the + // sharee. + $acl = parent::getACL(); + $acl[] = [ + 'privilege' => '{DAV:}read', + 'principal' => $this->calendarInfo['principaluri'], + 'protected' => true, + ]; + if ($this->calendarInfo['{http://sabredav.org/ns}read-only']) { + $acl[] = [ + 'privilege' => '{DAV:}write-properties', + 'principal' => $this->calendarInfo['principaluri'], + 'protected' => true, + ]; + } else { + $acl[] = [ + 'privilege' => '{DAV:}write', + 'principal' => $this->calendarInfo['principaluri'], + 'protected' => true, + ]; + } + return $acl; + + } + + /** + * This method returns the ACL's for calendar objects in this calendar. + * The result of this method automatically gets passed to the + * calendar-object nodes in the calendar. + * + * @return array + */ + function getChildACL() { + + $acl = parent::getChildACL(); + $acl[] = [ + 'privilege' => '{DAV:}read', + 'principal' => $this->calendarInfo['principaluri'], + 'protected' => true, + ]; + + if (!$this->calendarInfo['{http://sabredav.org/ns}read-only']) { + $acl[] = [ + 'privilege' => '{DAV:}write', + 'principal' => $this->calendarInfo['principaluri'], + 'protected' => true, + ]; + } + return $acl; + + } + + + /** + * Returns the list of people whom this calendar is shared with. + * + * Every element in this array should have the following properties: + * * href - Often a mailto: address + * * commonName - Optional, for example a first + last name + * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants. + * * readOnly - boolean + * * summary - Optional, a description for the share + * + * @return array + */ + function getShares() { + + return $this->caldavBackend->getShares($this->calendarInfo['id']); + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/SharingPlugin.php b/vendor/sabre/dav/lib/CalDAV/SharingPlugin.php new file mode 100644 index 000000000..5154fb1de --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/SharingPlugin.php @@ -0,0 +1,426 @@ +server = $server; + $server->resourceTypeMapping['Sabre\\CalDAV\\ISharedCalendar'] = '{' . Plugin::NS_CALENDARSERVER . '}shared'; + + array_push( + $this->server->protectedProperties, + '{' . Plugin::NS_CALENDARSERVER . '}invite', + '{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes', + '{' . Plugin::NS_CALENDARSERVER . '}shared-url' + ); + + $this->server->xml->elementMap['{' . Plugin::NS_CALENDARSERVER . '}share'] = 'Sabre\\CalDAV\\Xml\\Request\\Share'; + $this->server->xml->elementMap['{' . Plugin::NS_CALENDARSERVER . '}invite-reply'] = 'Sabre\\CalDAV\\Xml\\Request\\InviteReply'; + + $this->server->on('propFind', [$this, 'propFindEarly']); + $this->server->on('propFind', [$this, 'propFindLate'], 150); + $this->server->on('propPatch', [$this, 'propPatch'], 40); + $this->server->on('method:POST', [$this, 'httpPost']); + + } + + /** + * This event is triggered when properties are requested for a certain + * node. + * + * This allows us to inject any properties early. + * + * @param DAV\PropFind $propFind + * @param DAV\INode $node + * @return void + */ + function propFindEarly(DAV\PropFind $propFind, DAV\INode $node) { + + if ($node instanceof IShareableCalendar) { + + $propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}invite', function() use ($node) { + return new Xml\Property\Invite( + $node->getShares() + ); + }); + + } + + if ($node instanceof ISharedCalendar) { + + $propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}shared-url', function() use ($node) { + return new Href( + $node->getSharedUrl() + ); + }); + + $propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}invite', function() use ($node) { + + // Fetching owner information + $props = $this->server->getPropertiesForPath($node->getOwner(), [ + '{http://sabredav.org/ns}email-address', + '{DAV:}displayname', + ], 0); + + $ownerInfo = [ + 'href' => $node->getOwner(), + ]; + + if (isset($props[0][200])) { + + // We're mapping the internal webdav properties to the + // elements caldav-sharing expects. + if (isset($props[0][200]['{http://sabredav.org/ns}email-address'])) { + $ownerInfo['href'] = 'mailto:' . $props[0][200]['{http://sabredav.org/ns}email-address']; + } + if (isset($props[0][200]['{DAV:}displayname'])) { + $ownerInfo['commonName'] = $props[0][200]['{DAV:}displayname']; + } + + } + + return new Xml\Property\Invite( + $node->getShares(), + $ownerInfo + ); + + }); + + } + + } + + /** + * This method is triggered *after* all properties have been retrieved. + * This allows us to inject the correct resourcetype for calendars that + * have been shared. + * + * @param DAV\PropFind $propFind + * @param DAV\INode $node + * @return void + */ + function propFindLate(DAV\PropFind $propFind, DAV\INode $node) { + + if ($node instanceof IShareableCalendar) { + if ($rt = $propFind->get('{DAV:}resourcetype')) { + if (count($node->getShares()) > 0) { + $rt->add('{' . Plugin::NS_CALENDARSERVER . '}shared-owner'); + } + } + $propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes', function() { + return new Xml\Property\AllowedSharingModes(true, false); + }); + + } + + } + + /** + * This method is trigged when a user attempts to update a node's + * properties. + * + * A previous draft of the sharing spec stated that it was possible to use + * PROPPATCH to remove 'shared-owner' from the resourcetype, thus unsharing + * the calendar. + * + * Even though this is no longer in the current spec, we keep this around + * because OS X 10.7 may still make use of this feature. + * + * @param string $path + * @param DAV\PropPatch $propPatch + * @return void + */ + function propPatch($path, DAV\PropPatch $propPatch) { + + $node = $this->server->tree->getNodeForPath($path); + if (!$node instanceof IShareableCalendar) + return; + + $propPatch->handle('{DAV:}resourcetype', function($value) use ($node) { + if ($value->is('{' . Plugin::NS_CALENDARSERVER . '}shared-owner')) return false; + $shares = $node->getShares(); + $remove = []; + foreach ($shares as $share) { + $remove[] = $share['href']; + } + $node->updateShares([], $remove); + + return true; + + }); + + } + + /** + * We intercept this to handle POST requests on calendars. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return null|bool + */ + function httpPost(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + // Only handling xml + $contentType = $request->getHeader('Content-Type'); + if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false) + return; + + // Making sure the node exists + try { + $node = $this->server->tree->getNodeForPath($path); + } catch (DAV\Exception\NotFound $e) { + return; + } + + $requestBody = $request->getBodyAsString(); + + // If this request handler could not deal with this POST request, it + // will return 'null' and other plugins get a chance to handle the + // request. + // + // However, we already requested the full body. This is a problem, + // because a body can only be read once. This is why we preemptively + // re-populated the request body with the existing data. + $request->setBody($requestBody); + + $message = $this->server->xml->parse($requestBody, $request->getUrl(), $documentType); + + switch ($documentType) { + + // Dealing with the 'share' document, which modified invitees on a + // calendar. + case '{' . Plugin::NS_CALENDARSERVER . '}share' : + + // We can only deal with IShareableCalendar objects + if (!$node instanceof IShareableCalendar) { + return; + } + + $this->server->transactionType = 'post-calendar-share'; + + // Getting ACL info + $acl = $this->server->getPlugin('acl'); + + // If there's no ACL support, we allow everything + if ($acl) { + $acl->checkPrivileges($path, '{DAV:}write'); + } + + $node->updateShares($message->set, $message->remove); + + $response->setStatus(200); + // Adding this because sending a response body may cause issues, + // and I wanted some type of indicator the response was handled. + $response->setHeader('X-Sabre-Status', 'everything-went-well'); + + // Breaking the event chain + return false; + + // The invite-reply document is sent when the user replies to an + // invitation of a calendar share. + case '{' . Plugin::NS_CALENDARSERVER . '}invite-reply' : + + // This only works on the calendar-home-root node. + if (!$node instanceof CalendarHome) { + return; + } + $this->server->transactionType = 'post-invite-reply'; + + // Getting ACL info + $acl = $this->server->getPlugin('acl'); + + // If there's no ACL support, we allow everything + if ($acl) { + $acl->checkPrivileges($path, '{DAV:}write'); + } + + $url = $node->shareReply( + $message->href, + $message->status, + $message->calendarUri, + $message->inReplyTo, + $message->summary + ); + + $response->setStatus(200); + // Adding this because sending a response body may cause issues, + // and I wanted some type of indicator the response was handled. + $response->setHeader('X-Sabre-Status', 'everything-went-well'); + + if ($url) { + $writer = $this->server->xml->getWriter($this->server->getBaseUri()); + $writer->openMemory(); + $writer->startDocument(); + $writer->startElement('{' . Plugin::NS_CALENDARSERVER . '}shared-as'); + $writer->write(new Href($url)); + $writer->endElement(); + $response->setHeader('Content-Type', 'application/xml'); + $response->setBody($writer->outputMemory()); + + } + + // Breaking the event chain + return false; + + case '{' . Plugin::NS_CALENDARSERVER . '}publish-calendar' : + + // We can only deal with IShareableCalendar objects + if (!$node instanceof IShareableCalendar) { + return; + } + $this->server->transactionType = 'post-publish-calendar'; + + // Getting ACL info + $acl = $this->server->getPlugin('acl'); + + // If there's no ACL support, we allow everything + if ($acl) { + $acl->checkPrivileges($path, '{DAV:}write'); + } + + $node->setPublishStatus(true); + + // iCloud sends back the 202, so we will too. + $response->setStatus(202); + + // Adding this because sending a response body may cause issues, + // and I wanted some type of indicator the response was handled. + $response->setHeader('X-Sabre-Status', 'everything-went-well'); + + // Breaking the event chain + return false; + + case '{' . Plugin::NS_CALENDARSERVER . '}unpublish-calendar' : + + // We can only deal with IShareableCalendar objects + if (!$node instanceof IShareableCalendar) { + return; + } + $this->server->transactionType = 'post-unpublish-calendar'; + + // Getting ACL info + $acl = $this->server->getPlugin('acl'); + + // If there's no ACL support, we allow everything + if ($acl) { + $acl->checkPrivileges($path, '{DAV:}write'); + } + + $node->setPublishStatus(false); + + $response->setStatus(200); + + // Adding this because sending a response body may cause issues, + // and I wanted some type of indicator the response was handled. + $response->setHeader('X-Sabre-Status', 'everything-went-well'); + + // Breaking the event chain + return false; + + } + + + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Adds support for caldav-sharing.', + 'link' => 'http://sabre.io/dav/caldav-sharing/', + ]; + + } +} diff --git a/vendor/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php b/vendor/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php new file mode 100644 index 000000000..7ba259c7b --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php @@ -0,0 +1,40 @@ +resourceTypeMapping['Sabre\\CalDAV\\Subscriptions\\ISubscription'] = + '{http://calendarserver.org/ns/}subscribed'; + + $server->xml->elementMap['{http://calendarserver.org/ns/}source'] = + 'Sabre\\DAV\\Xml\\Property\\Href'; + + $server->on('propFind', [$this, 'propFind'], 150); + + } + + /** + * This method should return a list of server-features. + * + * This is for example 'versioning' and is added to the DAV: header + * in an OPTIONS response. + * + * @return array + */ + function getFeatures() { + + return ['calendarserver-subscribed']; + + } + + /** + * Triggered after properties have been fetched. + * + * @param PropFind $propFind + * @param INode $node + * @return void + */ + function propFind(PropFind $propFind, INode $node) { + + // There's a bunch of properties that must appear as a self-closing + // xml-element. This event handler ensures that this will be the case. + $props = [ + '{http://calendarserver.org/ns/}subscribed-strip-alarms', + '{http://calendarserver.org/ns/}subscribed-strip-attachments', + '{http://calendarserver.org/ns/}subscribed-strip-todos', + ]; + + foreach ($props as $prop) { + + if ($propFind->getStatus($prop) === 200) { + $propFind->set($prop, '', 200); + } + + } + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using \Sabre\DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'subscriptions'; + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'This plugin allows users to store iCalendar subscriptions in their calendar-home.', + 'link' => null, + ]; + + } +} diff --git a/vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php b/vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php new file mode 100644 index 000000000..ee53da2c6 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php @@ -0,0 +1,274 @@ +caldavBackend = $caldavBackend; + $this->subscriptionInfo = $subscriptionInfo; + + $required = [ + 'id', + 'uri', + 'principaluri', + 'source', + ]; + + foreach ($required as $r) { + if (!isset($subscriptionInfo[$r])) { + throw new \InvalidArgumentException('The ' . $r . ' field is required when creating a subscription node'); + } + } + + } + + /** + * Returns the name of the node. + * + * This is used to generate the url. + * + * @return string + */ + function getName() { + + return $this->subscriptionInfo['uri']; + + } + + /** + * Returns the last modification time + * + * @return int + */ + function getLastModified() { + + if (isset($this->subscriptionInfo['lastmodified'])) { + return $this->subscriptionInfo['lastmodified']; + } + + } + + /** + * Deletes the current node + * + * @return void + */ + function delete() { + + $this->caldavBackend->deleteSubscription( + $this->subscriptionInfo['id'] + ); + + } + + /** + * Returns an array with all the child nodes + * + * @return DAV\INode[] + */ + function getChildren() { + + return []; + + } + + /** + * Updates properties on this node. + * + * This method received a PropPatch object, which contains all the + * information about the update. + * + * To update specific properties, call the 'handle' method on this object. + * Read the PropPatch documentation for more information. + * + * @param PropPatch $propPatch + * @return void + */ + function propPatch(PropPatch $propPatch) { + + return $this->caldavBackend->updateSubscription( + $this->subscriptionInfo['id'], + $propPatch + ); + + } + + /** + * Returns a list of properties for this nodes. + * + * The properties list is a list of propertynames the client requested, + * encoded in clark-notation {xmlnamespace}tagname. + * + * If the array is empty, it means 'all properties' were requested. + * + * Note that it's fine to liberally give properties back, instead of + * conforming to the list of requested properties. + * The Server class will filter out the extra. + * + * @param array $properties + * @return void + */ + function getProperties($properties) { + + $r = []; + + foreach ($properties as $prop) { + + switch ($prop) { + case '{http://calendarserver.org/ns/}source' : + $r[$prop] = new Href($this->subscriptionInfo['source'], false); + break; + default : + if (array_key_exists($prop, $this->subscriptionInfo)) { + $r[$prop] = $this->subscriptionInfo[$prop]; + } + break; + } + + } + + return $r; + + } + + /** + * Returns the owner principal. + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->subscriptionInfo['principaluri']; + + } + + /** + * Returns a group principal. + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getGroup() { + + return null; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + return [ + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner() . '/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => $this->getOwner() . '/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner() . '/calendar-proxy-read', + 'protected' => true, + ] + ]; + + } + + /** + * Updates the ACL. + * + * This method will receive a list of new ACE's. + * + * @param array $acl + * @return void + */ + function setACL(array $acl) { + + throw new MethodNotAllowed('Changing ACL is not yet supported'); + + } + + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See \Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + function getSupportedPrivilegeSet() { + + return null; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php b/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php new file mode 100644 index 000000000..9babcf15c --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php @@ -0,0 +1,84 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $result = [ + 'contentType' => $reader->getAttribute('content-type') ?: 'text/calendar', + 'version' => $reader->getAttribute('version') ?: '2.0', + ]; + + $elems = (array)$reader->parseInnerTree(); + foreach ($elems as $elem) { + + switch ($elem['name']) { + case '{' . Plugin::NS_CALDAV . '}expand' : + + $result['expand'] = [ + 'start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null, + 'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null, + ]; + + if (!$result['expand']['start'] || !$result['expand']['end']) { + throw new BadRequest('The "start" and "end" attributes are required when expanding calendar-data'); + } + if ($result['expand']['end'] <= $result['expand']['start']) { + throw new BadRequest('The end-date must be larger than the start-date when expanding calendar-data'); + } + break; + } + + } + + return $result; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php b/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php new file mode 100644 index 000000000..c9b27dbfd --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php @@ -0,0 +1,97 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $result = [ + 'name' => null, + 'is-not-defined' => false, + 'comp-filters' => [], + 'prop-filters' => [], + 'time-range' => false, + ]; + + $att = $reader->parseAttributes(); + $result['name'] = $att['name']; + + $elems = $reader->parseInnerTree(); + + if (is_array($elems)) foreach ($elems as $elem) { + + switch ($elem['name']) { + + case '{' . Plugin::NS_CALDAV . '}comp-filter' : + $result['comp-filters'][] = $elem['value']; + break; + case '{' . Plugin::NS_CALDAV . '}prop-filter' : + $result['prop-filters'][] = $elem['value']; + break; + case '{' . Plugin::NS_CALDAV . '}is-not-defined' : + $result['is-not-defined'] = true; + break; + case '{' . Plugin::NS_CALDAV . '}time-range' : + if ($result['name'] === 'VCALENDAR') { + throw new BadRequest('You cannot add time-range filters on the VCALENDAR component'); + } + $result['time-range'] = [ + 'start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null, + 'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null, + ]; + if ($result['time-range']['start'] && $result['time-range']['end'] && $result['time-range']['end'] <= $result['time-range']['start']) { + throw new BadRequest('The end-date must be larger than the start-date'); + } + break; + + } + + } + + return $result; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php b/vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php new file mode 100644 index 000000000..eb7f564df --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php @@ -0,0 +1,82 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $result = [ + 'name' => null, + 'is-not-defined' => false, + 'text-match' => null, + ]; + + $att = $reader->parseAttributes(); + $result['name'] = $att['name']; + + $elems = $reader->parseInnerTree(); + + if (is_array($elems)) foreach ($elems as $elem) { + + switch ($elem['name']) { + + case '{' . Plugin::NS_CALDAV . '}is-not-defined' : + $result['is-not-defined'] = true; + break; + case '{' . Plugin::NS_CALDAV . '}text-match' : + $result['text-match'] = [ + 'negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes', + 'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;ascii-casemap', + 'value' => $elem['value'], + ]; + break; + + } + + } + + return $result; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php b/vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php new file mode 100644 index 000000000..4c2e1b172 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php @@ -0,0 +1,98 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $result = [ + 'name' => null, + 'is-not-defined' => false, + 'param-filters' => [], + 'text-match' => null, + 'time-range' => false, + ]; + + $att = $reader->parseAttributes(); + $result['name'] = $att['name']; + + $elems = $reader->parseInnerTree(); + + if (is_array($elems)) foreach ($elems as $elem) { + + switch ($elem['name']) { + + case '{' . Plugin::NS_CALDAV . '}param-filter' : + $result['param-filters'][] = $elem['value']; + break; + case '{' . Plugin::NS_CALDAV . '}is-not-defined' : + $result['is-not-defined'] = true; + break; + case '{' . Plugin::NS_CALDAV . '}time-range' : + $result['time-range'] = [ + 'start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null, + 'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null, + ]; + if ($result['time-range']['start'] && $result['time-range']['end'] && $result['time-range']['end'] <= $result['time-range']['start']) { + throw new BadRequest('The end-date must be larger than the start-date'); + } + break; + case '{' . Plugin::NS_CALDAV . '}text-match' : + $result['text-match'] = [ + 'negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes', + 'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;ascii-casemap', + 'value' => $elem['value'], + ]; + break; + + } + + } + + return $result; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php b/vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php new file mode 100644 index 000000000..7fb022e33 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php @@ -0,0 +1,307 @@ + $value) { + if (!property_exists($this, $key)) { + throw new \InvalidArgumentException('Unknown option: ' . $key); + } + $this->$key = $value; + } + + } + + /** + * The xmlSerialize metod is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + $writer->writeElement('{' . CalDAV\Plugin::NS_CALENDARSERVER . '}invite-notification'); + + } + + /** + * This method serializes the entire notification, as it is used in the + * response body. + * + * @param Writer $writer + * @return void + */ + function xmlSerializeFull(Writer $writer) { + + $cs = '{' . CalDAV\Plugin::NS_CALENDARSERVER . '}'; + + $this->dtStamp->setTimezone(new \DateTimezone('GMT')); + $writer->writeElement($cs . 'dtstamp', $this->dtStamp->format('Ymd\\THis\\Z')); + + $writer->startElement($cs . 'invite-notification'); + + $writer->writeElement($cs . 'uid', $this->id); + $writer->writeElement('{DAV:}href', $this->href); + + switch ($this->type) { + + case SharingPlugin::STATUS_ACCEPTED : + $writer->writeElement($cs . 'invite-accepted'); + break; + case SharingPlugin::STATUS_DECLINED : + $writer->writeElement($cs . 'invite-declined'); + break; + case SharingPlugin::STATUS_DELETED : + $writer->writeElement($cs . 'invite-deleted'); + break; + case SharingPlugin::STATUS_NORESPONSE : + $writer->writeElement($cs . 'invite-noresponse'); + break; + + } + + $writer->writeElement($cs . 'hosturl', [ + '{DAV:}href' => $writer->contextUri . $this->hostUrl + ]); + + if ($this->summary) { + $writer->writeElement($cs . 'summary', $this->summary); + } + + $writer->startElement($cs . 'access'); + if ($this->readOnly) { + $writer->writeElement($cs . 'read'); + } else { + $writer->writeElement($cs . 'read-write'); + } + $writer->endElement(); // access + + $writer->startElement($cs . 'organizer'); + // If the organizer contains a 'mailto:' part, it means it should be + // treated as absolute. + if (strtolower(substr($this->organizer, 0, 7)) === 'mailto:') { + $writer->writeElement('{DAV:}href', $this->organizer); + } else { + $writer->writeElement('{DAV:}href', $writer->contextUri . $this->organizer); + } + if ($this->commonName) { + $writer->writeElement($cs . 'common-name', $this->commonName); + } + if ($this->firstName) { + $writer->writeElement($cs . 'first-name', $this->firstName); + } + if ($this->lastName) { + $writer->writeElement($cs . 'last-name', $this->lastName); + } + $writer->endElement(); // organizer + + if ($this->commonName) { + $writer->writeElement($cs . 'organizer-cn', $this->commonName); + } + if ($this->firstName) { + $writer->writeElement($cs . 'organizer-first', $this->firstName); + } + if ($this->lastName) { + $writer->writeElement($cs . 'organizer-last', $this->lastName); + } + if ($this->supportedComponents) { + $writer->writeElement('{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set', $this->supportedComponents); + } + + $writer->endElement(); // invite-notification + + } + + /** + * Returns a unique id for this notification + * + * This is just the base url. This should generally be some kind of unique + * id. + * + * @return string + */ + function getId() { + + return $this->id; + + } + + /** + * Returns the ETag for this notification. + * + * The ETag must be surrounded by literal double-quotes. + * + * @return string + */ + function getETag() { + + return $this->etag; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php b/vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php new file mode 100644 index 000000000..945323fed --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php @@ -0,0 +1,212 @@ + $value) { + if (!property_exists($this, $key)) { + throw new \InvalidArgumentException('Unknown option: ' . $key); + } + $this->$key = $value; + } + + } + + /** + * The xmlSerialize metod is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + $writer->writeElement('{' . CalDAV\Plugin::NS_CALENDARSERVER . '}invite-reply'); + + } + + /** + * This method serializes the entire notification, as it is used in the + * response body. + * + * @param Writer $writer + * @return void + */ + function xmlSerializeFull(Writer $writer) { + + $cs = '{' . CalDAV\Plugin::NS_CALENDARSERVER . '}'; + + $this->dtStamp->setTimezone(new \DateTimezone('GMT')); + $writer->writeElement($cs . 'dtstamp', $this->dtStamp->format('Ymd\\THis\\Z')); + + $writer->startElement($cs . 'invite-reply'); + + $writer->writeElement($cs . 'uid', $this->id); + $writer->writeElement($cs . 'in-reply-to', $this->inReplyTo); + $writer->writeElement('{DAV:}href', $this->href); + + switch ($this->type) { + + case SharingPlugin::STATUS_ACCEPTED : + $writer->writeElement($cs . 'invite-accepted'); + break; + case SharingPlugin::STATUS_DECLINED : + $writer->writeElement($cs . 'invite-declined'); + break; + + } + + $writer->writeElement($cs . 'hosturl', [ + '{DAV:}href' => $writer->contextUri . $this->hostUrl + ]); + + if ($this->summary) { + $writer->writeElement($cs . 'summary', $this->summary); + } + $writer->endElement(); // invite-reply + + } + + /** + * Returns a unique id for this notification + * + * This is just the base url. This should generally be some kind of unique + * id. + * + * @return string + */ + function getId() { + + return $this->id; + + } + + /** + * Returns the ETag for this notification. + * + * The ETag must be surrounded by literal double-quotes. + * + * @return string + */ + function getETag() { + + return $this->etag; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php b/vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php new file mode 100644 index 000000000..1c08f12fd --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php @@ -0,0 +1,45 @@ +id = $id; + $this->type = $type; + $this->description = $description; + $this->href = $href; + $this->etag = $etag; + + } + + /** + * The serialize method is called during xml writing. + * + * It should use the $writer argument to encode this object into XML. + * + * Important note: it is not needed to create the parent element. The + * parent element is already created, and we only have to worry about + * attributes, child elements and text (if any). + * + * Important note 2: If you are writing any new elements, you are also + * responsible for closing them. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + switch ($this->type) { + case self::TYPE_LOW : + $type = 'low'; + break; + case self::TYPE_MEDIUM : + $type = 'medium'; + break; + default : + case self::TYPE_HIGH : + $type = 'high'; + break; + } + + $writer->startElement('{' . Plugin::NS_CALENDARSERVER . '}systemstatus'); + $writer->writeAttribute('type', $type); + $writer->endElement(); + + } + + /** + * This method serializes the entire notification, as it is used in the + * response body. + * + * @param Writer $writer + * @return void + */ + function xmlSerializeFull(Writer $writer) { + + $cs = '{' . Plugin::NS_CALENDARSERVER . '}'; + switch ($this->type) { + case self::TYPE_LOW : + $type = 'low'; + break; + case self::TYPE_MEDIUM : + $type = 'medium'; + break; + default : + case self::TYPE_HIGH : + $type = 'high'; + break; + } + + $writer->startElement($cs . 'systemstatus'); + $writer->writeAttribute('type', $type); + + + if ($this->description) { + $writer->writeElement($cs . 'description', $this->description); + } + if ($this->href) { + $writer->writeElement('{DAV:}href', $this->href); + } + + $writer->endElement(); // systemstatus + + } + + /** + * Returns a unique id for this notification + * + * This is just the base url. This should generally be some kind of unique + * id. + * + * @return string + */ + function getId() { + + return $this->id; + + } + + /* + * Returns the ETag for this notification. + * + * The ETag must be surrounded by literal double-quotes. + * + * @return string + */ + function getETag() { + + return $this->etag; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php b/vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php new file mode 100644 index 000000000..c2a2d565e --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php @@ -0,0 +1,87 @@ +canBeShared = $canBeShared; + $this->canBePublished = $canBePublished; + + } + + /** + * The xmlSerialize metod is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + if ($this->canBeShared) { + $writer->writeElement('{' . Plugin::NS_CALENDARSERVER . '}can-be-shared'); + } + if ($this->canBePublished) { + $writer->writeElement('{' . Plugin::NS_CALENDARSERVER . '}can-be-published'); + } + + } + + + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php b/vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php new file mode 100644 index 000000000..f577a9919 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php @@ -0,0 +1,80 @@ +emails = $emails; + + } + + /** + * Returns the email addresses + * + * @return array + */ + function getValue() { + + return $this->emails; + + } + + /** + * The xmlSerialize metod is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->emails as $email) { + + $writer->writeElement('{http://calendarserver.org/ns/}email-address', $email); + + } + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php b/vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php new file mode 100644 index 000000000..3ee053214 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php @@ -0,0 +1,252 @@ +users = $users; + $this->organizer = $organizer; + + } + + /** + * Returns the list of users, as it was passed to the constructor. + * + * @return array + */ + function getValue() { + + return $this->users; + + } + + /** + * The xmlSerialize metod is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + $cs = '{' . Plugin::NS_CALENDARSERVER . '}'; + + if (!is_null($this->organizer)) { + + $writer->startElement($cs . 'organizer'); + $writer->writeElement('{DAV:}href', $this->organizer['href']); + + if (isset($this->organizer['commonName']) && $this->organizer['commonName']) { + $writer->writeElement($cs . 'common-name', $this->organizer['commonName']); + } + if (isset($this->organizer['firstName']) && $this->organizer['firstName']) { + $writer->writeElement($cs . 'first-name', $this->organizer['firstName']); + } + if (isset($this->organizer['lastName']) && $this->organizer['lastName']) { + $writer->writeElement($cs . 'last-name', $this->organizer['lastName']); + } + $writer->endElement(); // organizer + + } + + foreach ($this->users as $user) { + + $writer->startElement($cs . 'user'); + $writer->writeElement('{DAV:}href', $user['href']); + if (isset($user['commonName']) && $user['commonName']) { + $writer->writeElement($cs . 'common-name', $user['commonName']); + } + switch ($user['status']) { + + case SharingPlugin::STATUS_ACCEPTED : + $writer->writeElement($cs . 'invite-accepted'); + break; + case SharingPlugin::STATUS_DECLINED : + $writer->writeElement($cs . 'invite-declined'); + break; + case SharingPlugin::STATUS_NORESPONSE : + $writer->writeElement($cs . 'invite-noresponse'); + break; + case SharingPlugin::STATUS_INVALID : + $writer->writeElement($cs . 'invite-invalid'); + break; + } + + $writer->startElement($cs . 'access'); + if ($user['readOnly']) { + $writer->writeElement($cs . 'read'); + } else { + $writer->writeElement($cs . 'read-write'); + } + $writer->endElement(); // access + + if (isset($user['summary']) && $user['summary']) { + $writer->writeElement($cs . 'summary', $user['summary']); + } + + $writer->endElement(); //user + + } + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $cs = '{' . Plugin::NS_CALENDARSERVER . '}'; + + $users = []; + + foreach ($reader->parseInnerTree() as $elem) { + + if ($elem['name'] !== $cs . 'user') + continue; + + $user = [ + 'href' => null, + 'commonName' => null, + 'readOnly' => null, + 'summary' => null, + 'status' => null, + ]; + + foreach ($elem['value'] as $userElem) { + + switch ($userElem['name']) { + case $cs . 'invite-accepted' : + $user['status'] = SharingPlugin::STATUS_ACCEPTED; + break; + case $cs . 'invite-declined' : + $user['status'] = SharingPlugin::STATUS_DECLINED; + break; + case $cs . 'invite-noresponse' : + $user['status'] = SharingPlugin::STATUS_NORESPONSE; + break; + case $cs . 'invite-invalid' : + $user['status'] = SharingPlugin::STATUS_INVALID; + break; + case '{DAV:}href' : + $user['href'] = $userElem['value']; + break; + case $cs . 'common-name' : + $user['commonName'] = $userElem['value']; + break; + case $cs . 'access' : + foreach ($userElem['value'] as $accessHref) { + if ($accessHref['name'] === $cs . 'read') { + $user['readOnly'] = true; + } + } + break; + case $cs . 'summary' : + $user['summary'] = $userElem['value']; + break; + + } + + } + if (!$user['status']) { + throw new \InvalidArgumentException('Every user must have one of cs:invite-accepted, cs:invite-declined, cs:invite-noresponse or cs:invite-invalid'); + } + + $users[] = $user; + + } + + return new self($users); + + } +} diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php b/vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php new file mode 100644 index 000000000..4a253e008 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php @@ -0,0 +1,140 @@ +value = $value; + + } + + /** + * Returns the current value + * + * @return string + */ + function getValue() { + + return $this->value; + + } + + /** + * The xmlSerialize metod is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + switch ($this->value) { + case self::TRANSPARENT : + $writer->writeElement('{' . Plugin::NS_CALDAV . '}transparent'); + break; + case self::OPAQUE : + $writer->writeElement('{' . Plugin::NS_CALDAV . '}opaque'); + break; + } + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elems = Elements::xmlDeserialize($reader); + + $value = null; + + foreach ($elems as $elem) { + switch ($elem) { + case '{' . Plugin::NS_CALDAV . '}opaque' : + $value = self::OPAQUE; + break; + case '{' . Plugin::NS_CALDAV . '}transparent' : + $value = self::TRANSPARENT; + break; + } + } + if (is_null($value)) + return null; + + return new self($value); + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php b/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php new file mode 100644 index 000000000..7a26e767e --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php @@ -0,0 +1,129 @@ +components = $components; + + } + + /** + * Returns the list of supported components + * + * @return array + */ + function getValue() { + + return $this->components; + + } + + /** + * The xmlSerialize metod is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->components as $component) { + + $writer->startElement('{' . Plugin::NS_CALDAV . '}comp'); + $writer->writeAttributes(['name' => $component]); + $writer->endElement(); + + } + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elems = $reader->parseInnerTree(); + + $components = []; + + foreach ((array)$elems as $elem) { + if ($elem['name'] === '{' . Plugin::NS_CALDAV . '}comp') { + $components[] = $elem['attributes']['name']; + } + } + + if (!$components) { + throw new ParseException('supported-calendar-component-set must have at least one CALDAV:comp element'); + } + + return new self($components); + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php b/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php new file mode 100644 index 000000000..b0c407fd6 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php @@ -0,0 +1,60 @@ +startElement('{' . Plugin::NS_CALDAV . '}calendar-data'); + $writer->writeAttributes([ + 'content-type' => 'text/calendar', + 'version' => '2.0', + ]); + $writer->endElement(); // calendar-data + $writer->startElement('{' . Plugin::NS_CALDAV . '}calendar-data'); + $writer->writeAttributes([ + 'content-type' => 'application/calendar+json', + ]); + $writer->endElement(); // calendar-data + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php b/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php new file mode 100644 index 000000000..71de25a62 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php @@ -0,0 +1,57 @@ +writeElement('{' . Plugin::NS_CALDAV . '}supported-collation', $collation); + } + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php b/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php new file mode 100644 index 000000000..79b3bb3ac --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php @@ -0,0 +1,124 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elems = $reader->parseInnerTree([ + '{urn:ietf:params:xml:ns:caldav}calendar-data' => 'Sabre\\CalDAV\\Xml\\Filter\\CalendarData', + '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue', + ]); + + $newProps = [ + 'hrefs' => [], + 'properties' => [], + ]; + + foreach ($elems as $elem) { + + switch ($elem['name']) { + + case '{DAV:}prop' : + $newProps['properties'] = array_keys($elem['value']); + if (isset($elem['value']['{' . Plugin::NS_CALDAV . '}calendar-data'])) { + $newProps += $elem['value']['{' . Plugin::NS_CALDAV . '}calendar-data']; + } + break; + case '{DAV:}href' : + $newProps['hrefs'][] = Uri\resolve($reader->contextUri, $elem['value']); + break; + + } + + } + + $obj = new self(); + foreach ($newProps as $key => $value) { + $obj->$key = $value; + } + + return $obj; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php b/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php new file mode 100644 index 000000000..848a4dc46 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php @@ -0,0 +1,139 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elems = $reader->parseInnerTree([ + '{urn:ietf:params:xml:ns:caldav}comp-filter' => 'Sabre\\CalDAV\\Xml\\Filter\\CompFilter', + '{urn:ietf:params:xml:ns:caldav}prop-filter' => 'Sabre\\CalDAV\\Xml\\Filter\\PropFilter', + '{urn:ietf:params:xml:ns:caldav}param-filter' => 'Sabre\\CalDAV\\Xml\\Filter\\ParamFilter', + '{urn:ietf:params:xml:ns:caldav}calendar-data' => 'Sabre\\CalDAV\\Xml\\Filter\\CalendarData', + '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue', + ]); + + $newProps = [ + 'filters' => null, + 'properties' => [], + ]; + + if (!is_array($elems)) $elems = []; + + foreach ($elems as $elem) { + + switch ($elem['name']) { + + case '{DAV:}prop' : + $newProps['properties'] = array_keys($elem['value']); + if (isset($elem['value']['{' . Plugin::NS_CALDAV . '}calendar-data'])) { + $newProps += $elem['value']['{' . Plugin::NS_CALDAV . '}calendar-data']; + } + break; + case '{' . Plugin::NS_CALDAV . '}filter' : + foreach ($elem['value'] as $subElem) { + if ($subElem['name'] === '{' . Plugin::NS_CALDAV . '}comp-filter') { + if (!is_null($newProps['filters'])) { + throw new BadRequest('Only one top-level comp-filter may be defined'); + } + $newProps['filters'] = $subElem['value']; + } + } + break; + + } + + } + + if (is_null($newProps['filters'])) { + throw new BadRequest('The {' . Plugin::NS_CALDAV . '}filter element is required for this request'); + } + + $obj = new self(); + foreach ($newProps as $key => $value) { + $obj->$key = $value; + } + return $obj; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php b/vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php new file mode 100644 index 000000000..e3b27d08e --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php @@ -0,0 +1,91 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $timeRange = '{' . Plugin::NS_CALDAV . '}time-range'; + + $start = null; + $end = null; + + foreach ((array)$reader->parseInnerTree([]) as $elem) { + + if ($elem['name'] !== $timeRange) continue; + + $start = empty($elem['attributes']['start']) ?: $elem['attributes']['start']; + $end = empty($elem['attributes']['end']) ?: $elem['attributes']['end']; + + } + if (!$start && !$end) { + throw new BadRequest('The freebusy report must have a time-range element'); + } + if ($start) { + $start = DateTimeParser::parseDateTime($start); + } + if ($end) { + $end = DateTimeParser::parseDateTime($end); + } + $result = new self(); + $result->start = $start; + $result->end = $end; + + return $result; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php b/vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php new file mode 100644 index 000000000..ec627156f --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php @@ -0,0 +1,149 @@ +href = $href; + $this->calendarUri = $calendarUri; + $this->inReplyTo = $inReplyTo; + $this->summary = $summary; + $this->status = $status; + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elems = KeyValue::xmlDeserialize($reader); + + $href = null; + $calendarUri = null; + $inReplyTo = null; + $summary = null; + $status = null; + + foreach ($elems as $name => $value) { + + switch ($name) { + + case '{' . Plugin::NS_CALENDARSERVER . '}hosturl' : + foreach ($value as $bla) { + if ($bla['name'] === '{DAV:}href') { + $calendarUri = $bla['value']; + } + } + break; + case '{' . Plugin::NS_CALENDARSERVER . '}invite-accepted' : + $status = SharingPlugin::STATUS_ACCEPTED; + break; + case '{' . Plugin::NS_CALENDARSERVER . '}invite-declined' : + $status = SharingPlugin::STATUS_DECLINED; + break; + case '{' . Plugin::NS_CALENDARSERVER . '}in-reply-to' : + $inReplyTo = $value; + break; + case '{' . Plugin::NS_CALENDARSERVER . '}summary' : + $summary = $value; + break; + case '{DAV:}href' : + $href = $value; + break; + } + + } + if (is_null($calendarUri)) { + throw new BadRequest('The {http://calendarserver.org/ns/}hosturl/{DAV:}href element must exist'); + } + + return new self($href, $calendarUri, $inReplyTo, $summary, $status); + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php b/vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php new file mode 100644 index 000000000..7b745db55 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php @@ -0,0 +1,79 @@ +properties; + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $self = new self(); + + $elementMap = $reader->elementMap; + $elementMap['{DAV:}prop'] = 'Sabre\DAV\Xml\Element\Prop'; + $elementMap['{DAV:}set'] = 'Sabre\Xml\Element\KeyValue'; + $elems = $reader->parseInnerTree($elementMap); + + foreach ($elems as $elem) { + if ($elem['name'] === '{DAV:}set') { + $self->properties = array_merge($self->properties, $elem['value']['{DAV:}prop']); + } + } + + return $self; + + } + +} diff --git a/vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php b/vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php new file mode 100644 index 000000000..dacc5dc94 --- /dev/null +++ b/vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php @@ -0,0 +1,116 @@ +set = $set; + $this->remove = $remove; + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elems = $reader->parseInnerTree([ + '{' . Plugin::NS_CALENDARSERVER . '}set' => 'Sabre\\Xml\\Element\\KeyValue', + '{' . Plugin::NS_CALENDARSERVER . '}remove' => 'Sabre\\Xml\\Element\\KeyValue', + ]); + + $set = []; + $remove = []; + + foreach ($elems as $elem) { + switch ($elem['name']) { + + case '{' . Plugin::NS_CALENDARSERVER . '}set' : + $sharee = $elem['value']; + + $sumElem = '{' . Plugin::NS_CALENDARSERVER . '}summary'; + $commonName = '{' . Plugin::NS_CALENDARSERVER . '}common-name'; + + $set[] = [ + 'href' => $sharee['{DAV:}href'], + 'commonName' => isset($sharee[$commonName]) ? $sharee[$commonName] : null, + 'summary' => isset($sharee[$sumElem]) ? $sharee[$sumElem] : null, + 'readOnly' => !array_key_exists('{' . Plugin::NS_CALENDARSERVER . '}read-write', $sharee), + ]; + break; + + case '{' . Plugin::NS_CALENDARSERVER . '}remove' : + $remove[] = $elem['value']['{DAV:}href']; + break; + + } + } + + return new self($set, $remove); + + } + +} diff --git a/vendor/sabre/dav/lib/CardDAV/AddressBook.php b/vendor/sabre/dav/lib/CardDAV/AddressBook.php new file mode 100644 index 000000000..70bec8760 --- /dev/null +++ b/vendor/sabre/dav/lib/CardDAV/AddressBook.php @@ -0,0 +1,433 @@ +carddavBackend = $carddavBackend; + $this->addressBookInfo = $addressBookInfo; + + } + + /** + * Returns the name of the addressbook + * + * @return string + */ + function getName() { + + return $this->addressBookInfo['uri']; + + } + + /** + * Returns a card + * + * @param string $name + * @return \ICard + */ + function getChild($name) { + + $obj = $this->carddavBackend->getCard($this->addressBookInfo['id'], $name); + if (!$obj) throw new DAV\Exception\NotFound('Card not found'); + return new Card($this->carddavBackend, $this->addressBookInfo, $obj); + + } + + /** + * Returns the full list of cards + * + * @return array + */ + function getChildren() { + + $objs = $this->carddavBackend->getCards($this->addressBookInfo['id']); + $children = []; + foreach ($objs as $obj) { + $obj['acl'] = $this->getChildACL(); + $children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj); + } + return $children; + + } + + /** + * This method receives a list of paths in it's first argument. + * It must return an array with Node objects. + * + * If any children are not found, you do not have to return them. + * + * @param string[] $paths + * @return array + */ + function getMultipleChildren(array $paths) { + + $objs = $this->carddavBackend->getMultipleCards($this->addressBookInfo['id'], $paths); + $children = []; + foreach ($objs as $obj) { + $obj['acl'] = $this->getChildACL(); + $children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj); + } + return $children; + + } + + /** + * Creates a new directory + * + * We actually block this, as subdirectories are not allowed in addressbooks. + * + * @param string $name + * @return void + */ + function createDirectory($name) { + + throw new DAV\Exception\MethodNotAllowed('Creating collections in addressbooks is not allowed'); + + } + + /** + * Creates a new file + * + * The contents of the new file must be a valid VCARD. + * + * This method may return an ETag. + * + * @param string $name + * @param resource $vcardData + * @return string|null + */ + function createFile($name, $vcardData = null) { + + if (is_resource($vcardData)) { + $vcardData = stream_get_contents($vcardData); + } + // Converting to UTF-8, if needed + $vcardData = DAV\StringUtil::ensureUTF8($vcardData); + + return $this->carddavBackend->createCard($this->addressBookInfo['id'], $name, $vcardData); + + } + + /** + * Deletes the entire addressbook. + * + * @return void + */ + function delete() { + + $this->carddavBackend->deleteAddressBook($this->addressBookInfo['id']); + + } + + /** + * Renames the addressbook + * + * @param string $newName + * @return void + */ + function setName($newName) { + + throw new DAV\Exception\MethodNotAllowed('Renaming addressbooks is not yet supported'); + + } + + /** + * Returns the last modification date as a unix timestamp. + * + * @return void + */ + function getLastModified() { + + return null; + + } + + /** + * Updates properties on this node. + * + * This method received a PropPatch object, which contains all the + * information about the update. + * + * To update specific properties, call the 'handle' method on this object. + * Read the PropPatch documentation for more information. + * + * @param DAV\PropPatch $propPatch + * @return void + */ + function propPatch(DAV\PropPatch $propPatch) { + + return $this->carddavBackend->updateAddressBook($this->addressBookInfo['id'], $propPatch); + + } + + /** + * Returns a list of properties for this nodes. + * + * The properties list is a list of propertynames the client requested, + * encoded in clark-notation {xmlnamespace}tagname + * + * If the array is empty, it means 'all properties' were requested. + * + * @param array $properties + * @return array + */ + function getProperties($properties) { + + $response = []; + foreach ($properties as $propertyName) { + + if (isset($this->addressBookInfo[$propertyName])) { + + $response[$propertyName] = $this->addressBookInfo[$propertyName]; + + } + + } + + return $response; + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->addressBookInfo['principaluri']; + + } + + /** + * Returns a group principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getGroup() { + + return null; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + return [ + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + + ]; + + } + + /** + * This method returns the ACL's for card nodes in this address book. + * The result of this method automatically gets passed to the + * card nodes in this address book. + * + * @return array + */ + function getChildACL() { + + return [ + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + ]; + + } + + /** + * Updates the ACL + * + * This method will receive a list of new ACE's. + * + * @param array $acl + * @return void + */ + function setACL(array $acl) { + + throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported'); + + } + + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + function getSupportedPrivilegeSet() { + + return null; + + } + + /** + * This method returns the current sync-token for this collection. + * This can be any string. + * + * If null is returned from this function, the plugin assumes there's no + * sync information available. + * + * @return string|null + */ + function getSyncToken() { + + if ( + $this->carddavBackend instanceof Backend\SyncSupport && + isset($this->addressBookInfo['{DAV:}sync-token']) + ) { + return $this->addressBookInfo['{DAV:}sync-token']; + } + if ( + $this->carddavBackend instanceof Backend\SyncSupport && + isset($this->addressBookInfo['{http://sabredav.org/ns}sync-token']) + ) { + return $this->addressBookInfo['{http://sabredav.org/ns}sync-token']; + } + + } + + /** + * The getChanges method returns all the changes that have happened, since + * the specified syncToken and the current collection. + * + * This function should return an array, such as the following: + * + * [ + * 'syncToken' => 'The current synctoken', + * 'added' => [ + * 'new.txt', + * ], + * 'modified' => [ + * 'modified.txt', + * ], + * 'deleted' => [ + * 'foo.php.bak', + * 'old.txt' + * ] + * ]; + * + * The syncToken property should reflect the *current* syncToken of the + * collection, as reported getSyncToken(). This is needed here too, to + * ensure the operation is atomic. + * + * If the syncToken is specified as null, this is an initial sync, and all + * members should be reported. + * + * The modified property is an array of nodenames that have changed since + * the last token. + * + * The deleted property is an array with nodenames, that have been deleted + * from collection. + * + * The second argument is basically the 'depth' of the report. If it's 1, + * you only have to report changes that happened only directly in immediate + * descendants. If it's 2, it should also include changes from the nodes + * below the child collections. (grandchildren) + * + * The third (optional) argument allows a client to specify how many + * results should be returned at most. If the limit is not specified, it + * should be treated as infinite. + * + * If the limit (infinite or not) is higher than you're willing to return, + * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. + * + * If the syncToken is expired (due to data cleanup) or unknown, you must + * return null. + * + * The limit is 'suggestive'. You are free to ignore it. + * + * @param string $syncToken + * @param int $syncLevel + * @param int $limit + * @return array + */ + function getChanges($syncToken, $syncLevel, $limit = null) { + + if (!$this->carddavBackend instanceof Backend\SyncSupport) { + return null; + } + + return $this->carddavBackend->getChangesForAddressBook( + $this->addressBookInfo['id'], + $syncToken, + $syncLevel, + $limit + ); + + } +} diff --git a/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php b/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php new file mode 100644 index 000000000..ebc251832 --- /dev/null +++ b/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php @@ -0,0 +1,263 @@ +carddavBackend = $carddavBackend; + $this->principalUri = $principalUri; + + } + + /** + * Returns the name of this object + * + * @return string + */ + function getName() { + + list(, $name) = Uri\split($this->principalUri); + return $name; + + } + + /** + * Updates the name of this object + * + * @param string $name + * @return void + */ + function setName($name) { + + throw new DAV\Exception\MethodNotAllowed(); + + } + + /** + * Deletes this object + * + * @return void + */ + function delete() { + + throw new DAV\Exception\MethodNotAllowed(); + + } + + /** + * Returns the last modification date + * + * @return int + */ + function getLastModified() { + + return null; + + } + + /** + * Creates a new file under this object. + * + * This is currently not allowed + * + * @param string $filename + * @param resource $data + * @return void + */ + function createFile($filename, $data = null) { + + throw new DAV\Exception\MethodNotAllowed('Creating new files in this collection is not supported'); + + } + + /** + * Creates a new directory under this object. + * + * This is currently not allowed. + * + * @param string $filename + * @return void + */ + function createDirectory($filename) { + + throw new DAV\Exception\MethodNotAllowed('Creating new collections in this collection is not supported'); + + } + + /** + * Returns a single addressbook, by name + * + * @param string $name + * @todo needs optimizing + * @return \AddressBook + */ + function getChild($name) { + + foreach ($this->getChildren() as $child) { + if ($name == $child->getName()) + return $child; + + } + throw new DAV\Exception\NotFound('Addressbook with name \'' . $name . '\' could not be found'); + + } + + /** + * Returns a list of addressbooks + * + * @return array + */ + function getChildren() { + + $addressbooks = $this->carddavBackend->getAddressBooksForUser($this->principalUri); + $objs = []; + foreach ($addressbooks as $addressbook) { + $objs[] = new AddressBook($this->carddavBackend, $addressbook); + } + return $objs; + + } + + /** + * Creates a new address book. + * + * @param string $name + * @param MkCol $mkCol + * @throws DAV\Exception\InvalidResourceType + * @return void + */ + function createExtendedCollection($name, MkCol $mkCol) { + + if (!$mkCol->hasResourceType('{' . Plugin::NS_CARDDAV . '}addressbook')) { + throw new DAV\Exception\InvalidResourceType('Unknown resourceType for this collection'); + } + $properties = $mkCol->getRemainingValues(); + $mkCol->setRemainingResultCode(201); + $this->carddavBackend->createAddressBook($this->principalUri, $name, $properties); + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->principalUri; + + } + + /** + * Returns a group principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getGroup() { + + return null; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + return [ + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->principalUri, + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => $this->principalUri, + 'protected' => true, + ], + ]; + + } + + /** + * Updates the ACL + * + * This method will receive a list of new ACE's. + * + * @param array $acl + * @return void + */ + function setACL(array $acl) { + + throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported'); + + } + + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + function getSupportedPrivilegeSet() { + + return null; + + } + +} diff --git a/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php b/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php new file mode 100644 index 000000000..4a33df4ec --- /dev/null +++ b/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php @@ -0,0 +1,80 @@ +carddavBackend = $carddavBackend; + parent::__construct($principalBackend, $principalPrefix); + + } + + /** + * Returns the name of the node + * + * @return string + */ + function getName() { + + return Plugin::ADDRESSBOOK_ROOT; + + } + + /** + * This method returns a node for a principal. + * + * The passed array contains principal information, and is guaranteed to + * at least contain a uri item. Other properties may or may not be + * supplied by the authentication backend. + * + * @param array $principal + * @return \Sabre\DAV\INode + */ + function getChildForPrincipal(array $principal) { + + return new AddressBookHome($this->carddavBackend, $principal['uri']); + + } + +} diff --git a/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php b/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php new file mode 100644 index 000000000..03d2346da --- /dev/null +++ b/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php @@ -0,0 +1,38 @@ +getCard($addressBookId, $uri); + }, $uris); + + } + +} diff --git a/vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php b/vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php new file mode 100644 index 000000000..b9691b906 --- /dev/null +++ b/vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php @@ -0,0 +1,187 @@ +pdo = $pdo; + + } + + /** + * Returns the list of addressbooks for a specific user. + * + * @param string $principalUri + * @return array + */ + function getAddressBooksForUser($principalUri) { + + $stmt = $this->pdo->prepare('SELECT id, uri, displayname, principaluri, description, synctoken FROM ' . $this->addressBooksTableName . ' WHERE principaluri = ?'); + $stmt->execute([$principalUri]); + + $addressBooks = []; + + foreach ($stmt->fetchAll() as $row) { + + $addressBooks[] = [ + 'id' => $row['id'], + 'uri' => $row['uri'], + 'principaluri' => $row['principaluri'], + '{DAV:}displayname' => $row['displayname'], + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'], + '{http://calendarserver.org/ns/}getctag' => $row['synctoken'], + '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0', + ]; + + } + + return $addressBooks; + + } + + + /** + * Updates properties for an address book. + * + * The list of mutations is stored in a Sabre\DAV\PropPatch object. + * To do the actual updates, you must tell this object which properties + * you're going to process with the handle() method. + * + * Calling the handle method is like telling the PropPatch object "I + * promise I can handle updating this property". + * + * Read the PropPatch documenation for more info and examples. + * + * @param string $addressBookId + * @param \Sabre\DAV\PropPatch $propPatch + * @return void + */ + function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) { + + $supportedProperties = [ + '{DAV:}displayname', + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description', + ]; + + $propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) { + + $updates = []; + foreach ($mutations as $property => $newValue) { + + switch ($property) { + case '{DAV:}displayname' : + $updates['displayname'] = $newValue; + break; + case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' : + $updates['description'] = $newValue; + break; + } + } + $query = 'UPDATE ' . $this->addressBooksTableName . ' SET '; + $first = true; + foreach ($updates as $key => $value) { + if ($first) { + $first = false; + } else { + $query .= ', '; + } + $query .= ' `' . $key . '` = :' . $key . ' '; + } + $query .= ' WHERE id = :addressbookid'; + + $stmt = $this->pdo->prepare($query); + $updates['addressbookid'] = $addressBookId; + + $stmt->execute($updates); + + $this->addChange($addressBookId, "", 2); + + return true; + + }); + + } + + /** + * Creates a new address book + * + * @param string $principalUri + * @param string $url Just the 'basename' of the url. + * @param array $properties + * @return int Last insert id + */ + function createAddressBook($principalUri, $url, array $properties) { + + $values = [ + 'displayname' => null, + 'description' => null, + 'principaluri' => $principalUri, + 'uri' => $url, + ]; + + foreach ($properties as $property => $newValue) { + + switch ($property) { + case '{DAV:}displayname' : + $values['displayname'] = $newValue; + break; + case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' : + $values['description'] = $newValue; + break; + default : + throw new DAV\Exception\BadRequest('Unknown property: ' . $property); + } + + } + + $query = 'INSERT INTO ' . $this->addressBooksTableName . ' (uri, displayname, description, principaluri, synctoken) VALUES (:uri, :displayname, :description, :principaluri, 1)'; + $stmt = $this->pdo->prepare($query); + $stmt->execute($values); + return $this->pdo->lastInsertId(); + + } + + /** + * Deletes an entire addressbook and all its contents + * + * @param int $addressBookId + * @return void + */ + function deleteAddressBook($addressBookId) { + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->cardsTableName . ' WHERE addressbookid = ?'); + $stmt->execute([$addressBookId]); + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->addressBooksTableName . ' WHERE id = ?'); + $stmt->execute([$addressBookId]); + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->addressBookChangesTableName . ' WHERE addressbookid = ?'); + $stmt->execute([$addressBookId]); + + } + + /** + * Returns all cards for a specific addressbook id. + * + * This method should return the following properties for each card: + * * carddata - raw vcard data + * * uri - Some unique url + * * lastmodified - A unix timestamp + * + * It's recommended to also return the following properties: + * * etag - A unique etag. This must change every time the card changes. + * * size - The size of the card in bytes. + * + * If these last two properties are provided, less time will be spent + * calculating them. If they are specified, you can also ommit carddata. + * This may speed up certain requests, especially with large cards. + * + * @param mixed $addressbookId + * @return array + */ + function getCards($addressbookId) { + + $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, size FROM ' . $this->cardsTableName . ' WHERE addressbookid = ?'); + $stmt->execute([$addressbookId]); + + $result = []; + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + $row['etag'] = '"' . $row['etag'] . '"'; + $result[] = $row; + } + return $result; + + } + + /** + * Returns a specfic card. + * + * The same set of properties must be returned as with getCards. The only + * exception is that 'carddata' is absolutely required. + * + * If the card does not exist, you must return false. + * + * @param mixed $addressBookId + * @param string $cardUri + * @return array + */ + function getCard($addressBookId, $cardUri) { + + $stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified, etag, size FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri = ? LIMIT 1'); + $stmt->execute([$addressBookId, $cardUri]); + + $result = $stmt->fetch(\PDO::FETCH_ASSOC); + + if (!$result) return false; + + $result['etag'] = '"' . $result['etag'] . '"'; + return $result; + + } + + /** + * Returns a list of cards. + * + * This method should work identical to getCard, but instead return all the + * cards in the list as an array. + * + * If the backend supports this, it may allow for some speed-ups. + * + * @param mixed $addressBookId + * @param array $uris + * @return array + */ + function getMultipleCards($addressBookId, array $uris) { + + $query = 'SELECT id, uri, lastmodified, etag, size, carddata FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri IN ('; + // Inserting a whole bunch of question marks + $query .= implode(',', array_fill(0, count($uris), '?')); + $query .= ')'; + + $stmt = $this->pdo->prepare($query); + $stmt->execute(array_merge([$addressBookId], $uris)); + $result = []; + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + $row['etag'] = '"' . $row['etag'] . '"'; + $result[] = $row; + } + return $result; + + } + + /** + * Creates a new card. + * + * The addressbook id will be passed as the first argument. This is the + * same id as it is returned from the getAddressBooksForUser method. + * + * The cardUri is a base uri, and doesn't include the full path. The + * cardData argument is the vcard body, and is passed as a string. + * + * It is possible to return an ETag from this method. This ETag is for the + * newly created resource, and must be enclosed with double quotes (that + * is, the string itself must contain the double quotes). + * + * You should only return the ETag if you store the carddata as-is. If a + * subsequent GET request on the same card does not have the same body, + * byte-by-byte and you did return an ETag here, clients tend to get + * confused. + * + * If you don't return an ETag, you can just return null. + * + * @param mixed $addressBookId + * @param string $cardUri + * @param string $cardData + * @return string|null + */ + function createCard($addressBookId, $cardUri, $cardData) { + + $stmt = $this->pdo->prepare('INSERT INTO ' . $this->cardsTableName . ' (carddata, uri, lastmodified, addressbookid, size, etag) VALUES (?, ?, ?, ?, ?, ?)'); + + $etag = md5($cardData); + + $stmt->execute([ + $cardData, + $cardUri, + time(), + $addressBookId, + strlen($cardData), + $etag, + ]); + + $this->addChange($addressBookId, $cardUri, 1); + + return '"' . $etag . '"'; + + } + + /** + * Updates a card. + * + * The addressbook id will be passed as the first argument. This is the + * same id as it is returned from the getAddressBooksForUser method. + * + * The cardUri is a base uri, and doesn't include the full path. The + * cardData argument is the vcard body, and is passed as a string. + * + * It is possible to return an ETag from this method. This ETag should + * match that of the updated resource, and must be enclosed with double + * quotes (that is: the string itself must contain the actual quotes). + * + * You should only return the ETag if you store the carddata as-is. If a + * subsequent GET request on the same card does not have the same body, + * byte-by-byte and you did return an ETag here, clients tend to get + * confused. + * + * If you don't return an ETag, you can just return null. + * + * @param mixed $addressBookId + * @param string $cardUri + * @param string $cardData + * @return string|null + */ + function updateCard($addressBookId, $cardUri, $cardData) { + + $stmt = $this->pdo->prepare('UPDATE ' . $this->cardsTableName . ' SET carddata = ?, lastmodified = ?, size = ?, etag = ? WHERE uri = ? AND addressbookid =?'); + + $etag = md5($cardData); + $stmt->execute([ + $cardData, + time(), + strlen($cardData), + $etag, + $cardUri, + $addressBookId + ]); + + $this->addChange($addressBookId, $cardUri, 2); + + return '"' . $etag . '"'; + + } + + /** + * Deletes a card + * + * @param mixed $addressBookId + * @param string $cardUri + * @return bool + */ + function deleteCard($addressBookId, $cardUri) { + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri = ?'); + $stmt->execute([$addressBookId, $cardUri]); + + $this->addChange($addressBookId, $cardUri, 3); + + return $stmt->rowCount() === 1; + + } + + /** + * The getChanges method returns all the changes that have happened, since + * the specified syncToken in the specified address book. + * + * This function should return an array, such as the following: + * + * [ + * 'syncToken' => 'The current synctoken', + * 'added' => [ + * 'new.txt', + * ], + * 'modified' => [ + * 'updated.txt', + * ], + * 'deleted' => [ + * 'foo.php.bak', + * 'old.txt' + * ] + * ]; + * + * The returned syncToken property should reflect the *current* syncToken + * of the addressbook, as reported in the {http://sabredav.org/ns}sync-token + * property. This is needed here too, to ensure the operation is atomic. + * + * If the $syncToken argument is specified as null, this is an initial + * sync, and all members should be reported. + * + * The modified property is an array of nodenames that have changed since + * the last token. + * + * The deleted property is an array with nodenames, that have been deleted + * from collection. + * + * The $syncLevel argument is basically the 'depth' of the report. If it's + * 1, you only have to report changes that happened only directly in + * immediate descendants. If it's 2, it should also include changes from + * the nodes below the child collections. (grandchildren) + * + * The $limit argument allows a client to specify how many results should + * be returned at most. If the limit is not specified, it should be treated + * as infinite. + * + * If the limit (infinite or not) is higher than you're willing to return, + * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. + * + * If the syncToken is expired (due to data cleanup) or unknown, you must + * return null. + * + * The limit is 'suggestive'. You are free to ignore it. + * + * @param string $addressBookId + * @param string $syncToken + * @param int $syncLevel + * @param int $limit + * @return array + */ + function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) { + + // Current synctoken + $stmt = $this->pdo->prepare('SELECT synctoken FROM ' . $this->addressBooksTableName . ' WHERE id = ?'); + $stmt->execute([ $addressBookId ]); + $currentToken = $stmt->fetchColumn(0); + + if (is_null($currentToken)) return null; + + $result = [ + 'syncToken' => $currentToken, + 'added' => [], + 'modified' => [], + 'deleted' => [], + ]; + + if ($syncToken) { + + $query = "SELECT uri, operation FROM " . $this->addressBookChangesTableName . " WHERE synctoken >= ? AND synctoken < ? AND addressbookid = ? ORDER BY synctoken"; + if ($limit > 0) $query .= " LIMIT " . (int)$limit; + + // Fetching all changes + $stmt = $this->pdo->prepare($query); + $stmt->execute([$syncToken, $currentToken, $addressBookId]); + + $changes = []; + + // This loop ensures that any duplicates are overwritten, only the + // last change on a node is relevant. + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + + $changes[$row['uri']] = $row['operation']; + + } + + foreach ($changes as $uri => $operation) { + + switch ($operation) { + case 1: + $result['added'][] = $uri; + break; + case 2: + $result['modified'][] = $uri; + break; + case 3: + $result['deleted'][] = $uri; + break; + } + + } + } else { + // No synctoken supplied, this is the initial sync. + $query = "SELECT uri FROM " . $this->cardsTableName . " WHERE addressbookid = ?"; + $stmt = $this->pdo->prepare($query); + $stmt->execute([$addressBookId]); + + $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN); + } + return $result; + + } + + /** + * Adds a change record to the addressbookchanges table. + * + * @param mixed $addressBookId + * @param string $objectUri + * @param int $operation 1 = add, 2 = modify, 3 = delete + * @return void + */ + protected function addChange($addressBookId, $objectUri, $operation) { + + $stmt = $this->pdo->prepare('INSERT INTO ' . $this->addressBookChangesTableName . ' (uri, synctoken, addressbookid, operation) SELECT ?, synctoken, ?, ? FROM ' . $this->addressBooksTableName . ' WHERE id = ?'); + $stmt->execute([ + $objectUri, + $addressBookId, + $operation, + $addressBookId + ]); + $stmt = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET synctoken = synctoken + 1 WHERE id = ?'); + $stmt->execute([ + $addressBookId + ]); + + } +} diff --git a/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php b/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php new file mode 100644 index 000000000..f80618a8e --- /dev/null +++ b/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php @@ -0,0 +1,81 @@ + 'The current synctoken', + * 'added' => [ + * 'new.txt', + * ], + * 'modified' => [ + * 'modified.txt', + * ], + * 'deleted' => [ + * 'foo.php.bak', + * 'old.txt' + * ] + * ]; + * + * The returned syncToken property should reflect the *current* syncToken + * of the calendar, as reported in the {http://sabredav.org/ns}sync-token + * property. This is needed here too, to ensure the operation is atomic. + * + * If the $syncToken argument is specified as null, this is an initial + * sync, and all members should be reported. + * + * The modified property is an array of nodenames that have changed since + * the last token. + * + * The deleted property is an array with nodenames, that have been deleted + * from collection. + * + * The $syncLevel argument is basically the 'depth' of the report. If it's + * 1, you only have to report changes that happened only directly in + * immediate descendants. If it's 2, it should also include changes from + * the nodes below the child collections. (grandchildren) + * + * The $limit argument allows a client to specify how many results should + * be returned at most. If the limit is not specified, it should be treated + * as infinite. + * + * If the limit (infinite or not) is higher than you're willing to return, + * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. + * + * If the syncToken is expired (due to data cleanup) or unknown, you must + * return null. + * + * The limit is 'suggestive'. You are free to ignore it. + * + * @param string $addressBookId + * @param string $syncToken + * @param int $syncLevel + * @param int $limit + * @return array + */ + function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null); + +} diff --git a/vendor/sabre/dav/lib/CardDAV/Card.php b/vendor/sabre/dav/lib/CardDAV/Card.php new file mode 100644 index 000000000..8da672502 --- /dev/null +++ b/vendor/sabre/dav/lib/CardDAV/Card.php @@ -0,0 +1,263 @@ +carddavBackend = $carddavBackend; + $this->addressBookInfo = $addressBookInfo; + $this->cardData = $cardData; + + } + + /** + * Returns the uri for this object + * + * @return string + */ + function getName() { + + return $this->cardData['uri']; + + } + + /** + * Returns the VCard-formatted object + * + * @return string + */ + function get() { + + // Pre-populating 'carddata' is optional. If we don't yet have it + // already, we fetch it from the backend. + if (!isset($this->cardData['carddata'])) { + $this->cardData = $this->carddavBackend->getCard($this->addressBookInfo['id'], $this->cardData['uri']); + } + return $this->cardData['carddata']; + + } + + /** + * Updates the VCard-formatted object + * + * @param string $cardData + * @return string|null + */ + function put($cardData) { + + if (is_resource($cardData)) + $cardData = stream_get_contents($cardData); + + // Converting to UTF-8, if needed + $cardData = DAV\StringUtil::ensureUTF8($cardData); + + $etag = $this->carddavBackend->updateCard($this->addressBookInfo['id'], $this->cardData['uri'], $cardData); + $this->cardData['carddata'] = $cardData; + $this->cardData['etag'] = $etag; + + return $etag; + + } + + /** + * Deletes the card + * + * @return void + */ + function delete() { + + $this->carddavBackend->deleteCard($this->addressBookInfo['id'], $this->cardData['uri']); + + } + + /** + * Returns the mime content-type + * + * @return string + */ + function getContentType() { + + return 'text/vcard; charset=utf-8'; + + } + + /** + * Returns an ETag for this object + * + * @return string + */ + function getETag() { + + if (isset($this->cardData['etag'])) { + return $this->cardData['etag']; + } else { + $data = $this->get(); + if (is_string($data)) { + return '"' . md5($data) . '"'; + } else { + // We refuse to calculate the md5 if it's a stream. + return null; + } + } + + } + + /** + * Returns the last modification date as a unix timestamp + * + * @return int + */ + function getLastModified() { + + return isset($this->cardData['lastmodified']) ? $this->cardData['lastmodified'] : null; + + } + + /** + * Returns the size of this object in bytes + * + * @return int + */ + function getSize() { + + if (array_key_exists('size', $this->cardData)) { + return $this->cardData['size']; + } else { + return strlen($this->get()); + } + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->addressBookInfo['principaluri']; + + } + + /** + * Returns a group principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getGroup() { + + return null; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + // An alternative acl may be specified through the cardData array. + if (isset($this->cardData['acl'])) { + return $this->cardData['acl']; + } + + return [ + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->addressBookInfo['principaluri'], + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => $this->addressBookInfo['principaluri'], + 'protected' => true, + ], + ]; + + } + + /** + * Updates the ACL + * + * This method will receive a list of new ACE's. + * + * @param array $acl + * @return void + */ + function setACL(array $acl) { + + throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported'); + + } + + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + function getSupportedPrivilegeSet() { + + return null; + + } + +} diff --git a/vendor/sabre/dav/lib/CardDAV/IAddressBook.php b/vendor/sabre/dav/lib/CardDAV/IAddressBook.php new file mode 100644 index 000000000..f80e05575 --- /dev/null +++ b/vendor/sabre/dav/lib/CardDAV/IAddressBook.php @@ -0,0 +1,18 @@ +on('propFind', [$this, 'propFindEarly']); + $server->on('propFind', [$this, 'propFindLate'], 150); + $server->on('report', [$this, 'report']); + $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']); + $server->on('beforeWriteContent', [$this, 'beforeWriteContent']); + $server->on('beforeCreateFile', [$this, 'beforeCreateFile']); + $server->on('afterMethod:GET', [$this, 'httpAfterGet']); + + $server->xml->namespaceMap[self::NS_CARDDAV] = 'card'; + + $server->xml->elementMap['{' . self::NS_CARDDAV . '}addressbook-query'] = 'Sabre\\CardDAV\\Xml\\Request\\AddressBookQueryReport'; + $server->xml->elementMap['{' . self::NS_CARDDAV . '}addressbook-multiget'] = 'Sabre\\CardDAV\\Xml\\Request\\AddressBookMultiGetReport'; + + /* Mapping Interfaces to {DAV:}resourcetype values */ + $server->resourceTypeMapping['Sabre\\CardDAV\\IAddressBook'] = '{' . self::NS_CARDDAV . '}addressbook'; + $server->resourceTypeMapping['Sabre\\CardDAV\\IDirectory'] = '{' . self::NS_CARDDAV . '}directory'; + + /* Adding properties that may never be changed */ + $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}supported-address-data'; + $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}max-resource-size'; + $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}addressbook-home-set'; + $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}supported-collation-set'; + + $server->xml->elementMap['{http://calendarserver.org/ns/}me-card'] = 'Sabre\\DAV\\Xml\\Property\\Href'; + + $this->server = $server; + + } + + /** + * Returns a list of supported features. + * + * This is used in the DAV: header in the OPTIONS and PROPFIND requests. + * + * @return array + */ + function getFeatures() { + + return ['addressbook']; + + } + + /** + * Returns a list of reports this plugin supports. + * + * This will be used in the {DAV:}supported-report-set property. + * Note that you still need to subscribe to the 'report' event to actually + * implement them + * + * @param string $uri + * @return array + */ + function getSupportedReportSet($uri) { + + $node = $this->server->tree->getNodeForPath($uri); + if ($node instanceof IAddressBook || $node instanceof ICard) { + return [ + '{' . self::NS_CARDDAV . '}addressbook-multiget', + '{' . self::NS_CARDDAV . '}addressbook-query', + ]; + } + return []; + + } + + + /** + * Adds all CardDAV-specific properties + * + * @param DAV\PropFind $propFind + * @param DAV\INode $node + * @return void + */ + function propFindEarly(DAV\PropFind $propFind, DAV\INode $node) { + + $ns = '{' . self::NS_CARDDAV . '}'; + + if ($node instanceof IAddressBook) { + + $propFind->handle($ns . 'max-resource-size', $this->maxResourceSize); + $propFind->handle($ns . 'supported-address-data', function() { + return new Xml\Property\SupportedAddressData(); + }); + $propFind->handle($ns . 'supported-collation-set', function() { + return new Xml\Property\SupportedCollationSet(); + }); + + } + if ($node instanceof DAVACL\IPrincipal) { + + $path = $propFind->getPath(); + + $propFind->handle('{' . self::NS_CARDDAV . '}addressbook-home-set', function() use ($path) { + return new Href($this->getAddressBookHomeForPrincipal($path) . '/'); + }); + + if ($this->directories) $propFind->handle('{' . self::NS_CARDDAV . '}directory-gateway', function() { + return new Href($this->directories); + }); + + } + + if ($node instanceof ICard) { + + // The address-data property is not supposed to be a 'real' + // property, but in large chunks of the spec it does act as such. + // Therefore we simply expose it as a property. + $propFind->handle('{' . self::NS_CARDDAV . '}address-data', function() use ($node) { + $val = $node->get(); + if (is_resource($val)) + $val = stream_get_contents($val); + + return $val; + + }); + + } + + } + + /** + * This functions handles REPORT requests specific to CardDAV + * + * @param string $reportName + * @param \DOMNode $dom + * @param mixed $path + * @return bool + */ + function report($reportName, $dom, $path) { + + switch ($reportName) { + case '{' . self::NS_CARDDAV . '}addressbook-multiget' : + $this->server->transactionType = 'report-addressbook-multiget'; + $this->addressbookMultiGetReport($dom); + return false; + case '{' . self::NS_CARDDAV . '}addressbook-query' : + $this->server->transactionType = 'report-addressbook-query'; + $this->addressBookQueryReport($dom); + return false; + default : + return; + + } + + + } + + /** + * Returns the addressbook home for a given principal + * + * @param string $principal + * @return string + */ + protected function getAddressbookHomeForPrincipal($principal) { + + list(, $principalId) = \Sabre\HTTP\URLUtil::splitPath($principal); + return self::ADDRESSBOOK_ROOT . '/' . $principalId; + + } + + + /** + * This function handles the addressbook-multiget REPORT. + * + * This report is used by the client to fetch the content of a series + * of urls. Effectively avoiding a lot of redundant requests. + * + * @param Xml\Request\AddressBookMultiGetReport $report + * @return void + */ + function addressbookMultiGetReport($report) { + + $contentType = $report->contentType; + $version = $report->version; + if ($version) { + $contentType .= '; version=' . $version; + } + + $vcardType = $this->negotiateVCard( + $contentType + ); + + $propertyList = []; + $paths = array_map( + [$this->server, 'calculateUri'], + $report->hrefs + ); + foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $props) { + + if (isset($props['200']['{' . self::NS_CARDDAV . '}address-data'])) { + + $props['200']['{' . self::NS_CARDDAV . '}address-data'] = $this->convertVCard( + $props[200]['{' . self::NS_CARDDAV . '}address-data'], + $vcardType + ); + + } + $propertyList[] = $props; + + } + + $prefer = $this->server->getHTTPPrefer(); + + $this->server->httpResponse->setStatus(207); + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer'); + $this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, $prefer['return'] === 'minimal')); + + } + + /** + * This method is triggered before a file gets updated with new content. + * + * This plugin uses this method to ensure that Card nodes receive valid + * vcard data. + * + * @param string $path + * @param DAV\IFile $node + * @param resource $data + * @param bool $modified Should be set to true, if this event handler + * changed &$data. + * @return void + */ + function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified) { + + if (!$node instanceof ICard) + return; + + $this->validateVCard($data, $modified); + + } + + /** + * This method is triggered before a new file is created. + * + * This plugin uses this method to ensure that Card nodes receive valid + * vcard data. + * + * @param string $path + * @param resource $data + * @param DAV\ICollection $parentNode + * @param bool $modified Should be set to true, if this event handler + * changed &$data. + * @return void + */ + function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified) { + + if (!$parentNode instanceof IAddressBook) + return; + + $this->validateVCard($data, $modified); + + } + + /** + * Checks if the submitted iCalendar data is in fact, valid. + * + * An exception is thrown if it's not. + * + * @param resource|string $data + * @param bool $modified Should be set to true, if this event handler + * changed &$data. + * @return void + */ + protected function validateVCard(&$data, &$modified) { + + // If it's a stream, we convert it to a string first. + if (is_resource($data)) { + $data = stream_get_contents($data); + } + + $before = md5($data); + + // Converting the data to unicode, if needed. + $data = DAV\StringUtil::ensureUTF8($data); + + if (md5($data) !== $before) $modified = true; + + try { + + // If the data starts with a [, we can reasonably assume we're dealing + // with a jCal object. + if (substr($data, 0, 1) === '[') { + $vobj = VObject\Reader::readJson($data); + + // Converting $data back to iCalendar, as that's what we + // technically support everywhere. + $data = $vobj->serialize(); + $modified = true; + } else { + $vobj = VObject\Reader::read($data); + } + + } catch (VObject\ParseException $e) { + + throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid vCard or jCard data. Parse error: ' . $e->getMessage()); + + } + + if ($vobj->name !== 'VCARD') { + throw new DAV\Exception\UnsupportedMediaType('This collection can only support vcard objects.'); + } + + if (!isset($vobj->UID)) { + // No UID in vcards is invalid, but we'll just add it in anyway. + $vobj->add('UID', DAV\UUIDUtil::getUUID()); + $data = $vobj->serialize(); + $modified = true; + } + + // Destroy circular references to PHP will GC the object. + $vobj->destroy(); + } + + + /** + * This function handles the addressbook-query REPORT + * + * This report is used by the client to filter an addressbook based on a + * complex query. + * + * @param Xml\Request\AddressBookQueryReport $report + * @return void + */ + protected function addressbookQueryReport($report) { + + $depth = $this->server->getHTTPDepth(0); + + if ($depth == 0) { + $candidateNodes = [ + $this->server->tree->getNodeForPath($this->server->getRequestUri()) + ]; + if (!$candidateNodes[0] instanceof ICard) { + throw new ReportNotSupported('The addressbook-query report is not supported on this url with Depth: 0'); + } + } else { + $candidateNodes = $this->server->tree->getChildren($this->server->getRequestUri()); + } + + $contentType = $report->contentType; + if ($report->version) { + $contentType .= '; version=' . $report->version; + } + + $vcardType = $this->negotiateVCard( + $contentType + ); + + $validNodes = []; + foreach ($candidateNodes as $node) { + + if (!$node instanceof ICard) + continue; + + $blob = $node->get(); + if (is_resource($blob)) { + $blob = stream_get_contents($blob); + } + + if (!$this->validateFilters($blob, $report->filters, $report->test)) { + continue; + } + + $validNodes[] = $node; + + if ($report->limit && $report->limit <= count($validNodes)) { + // We hit the maximum number of items, we can stop now. + break; + } + + } + + $result = []; + foreach ($validNodes as $validNode) { + + if ($depth == 0) { + $href = $this->server->getRequestUri(); + } else { + $href = $this->server->getRequestUri() . '/' . $validNode->getName(); + } + + list($props) = $this->server->getPropertiesForPath($href, $report->properties, 0); + + if (isset($props[200]['{' . self::NS_CARDDAV . '}address-data'])) { + + $props[200]['{' . self::NS_CARDDAV . '}address-data'] = $this->convertVCard( + $props[200]['{' . self::NS_CARDDAV . '}address-data'], + $vcardType + ); + + } + $result[] = $props; + + } + + $prefer = $this->server->getHTTPPrefer(); + + $this->server->httpResponse->setStatus(207); + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer'); + $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return'] === 'minimal')); + + } + + /** + * Validates if a vcard makes it throught a list of filters. + * + * @param string $vcardData + * @param array $filters + * @param string $test anyof or allof (which means OR or AND) + * @return bool + */ + function validateFilters($vcardData, array $filters, $test) { + + + if (!$filters) return true; + $vcard = VObject\Reader::read($vcardData); + + foreach ($filters as $filter) { + + $isDefined = isset($vcard->{$filter['name']}); + if ($filter['is-not-defined']) { + if ($isDefined) { + $success = false; + } else { + $success = true; + } + } elseif ((!$filter['param-filters'] && !$filter['text-matches']) || !$isDefined) { + + // We only need to check for existence + $success = $isDefined; + + } else { + + $vProperties = $vcard->select($filter['name']); + + $results = []; + if ($filter['param-filters']) { + $results[] = $this->validateParamFilters($vProperties, $filter['param-filters'], $filter['test']); + } + if ($filter['text-matches']) { + $texts = []; + foreach ($vProperties as $vProperty) + $texts[] = $vProperty->getValue(); + + $results[] = $this->validateTextMatches($texts, $filter['text-matches'], $filter['test']); + } + + if (count($results) === 1) { + $success = $results[0]; + } else { + if ($filter['test'] === 'anyof') { + $success = $results[0] || $results[1]; + } else { + $success = $results[0] && $results[1]; + } + } + + } // else + + // There are two conditions where we can already determine whether + // or not this filter succeeds. + if ($test === 'anyof' && $success) { + + // Destroy circular references to PHP will GC the object. + $vcard->destroy(); + + return true; + } + if ($test === 'allof' && !$success) { + + // Destroy circular references to PHP will GC the object. + $vcard->destroy(); + + return false; + } + + } // foreach + + + // Destroy circular references to PHP will GC the object. + $vcard->destroy(); + + // If we got all the way here, it means we haven't been able to + // determine early if the test failed or not. + // + // This implies for 'anyof' that the test failed, and for 'allof' that + // we succeeded. Sounds weird, but makes sense. + return $test === 'allof'; + + } + + /** + * Validates if a param-filter can be applied to a specific property. + * + * @todo currently we're only validating the first parameter of the passed + * property. Any subsequence parameters with the same name are + * ignored. + * @param array $vProperties + * @param array $filters + * @param string $test + * @return bool + */ + protected function validateParamFilters(array $vProperties, array $filters, $test) { + + foreach ($filters as $filter) { + + $isDefined = false; + foreach ($vProperties as $vProperty) { + $isDefined = isset($vProperty[$filter['name']]); + if ($isDefined) break; + } + + if ($filter['is-not-defined']) { + if ($isDefined) { + $success = false; + } else { + $success = true; + } + + // If there's no text-match, we can just check for existence + } elseif (!$filter['text-match'] || !$isDefined) { + + $success = $isDefined; + + } else { + + $success = false; + foreach ($vProperties as $vProperty) { + // If we got all the way here, we'll need to validate the + // text-match filter. + $success = DAV\StringUtil::textMatch($vProperty[$filter['name']]->getValue(), $filter['text-match']['value'], $filter['text-match']['collation'], $filter['text-match']['match-type']); + if ($success) break; + } + if ($filter['text-match']['negate-condition']) { + $success = !$success; + } + + } // else + + // There are two conditions where we can already determine whether + // or not this filter succeeds. + if ($test === 'anyof' && $success) { + return true; + } + if ($test === 'allof' && !$success) { + return false; + } + + } + + // If we got all the way here, it means we haven't been able to + // determine early if the test failed or not. + // + // This implies for 'anyof' that the test failed, and for 'allof' that + // we succeeded. Sounds weird, but makes sense. + return $test === 'allof'; + + } + + /** + * Validates if a text-filter can be applied to a specific property. + * + * @param array $texts + * @param array $filters + * @param string $test + * @return bool + */ + protected function validateTextMatches(array $texts, array $filters, $test) { + + foreach ($filters as $filter) { + + $success = false; + foreach ($texts as $haystack) { + $success = DAV\StringUtil::textMatch($haystack, $filter['value'], $filter['collation'], $filter['match-type']); + + // Breaking on the first match + if ($success) break; + } + if ($filter['negate-condition']) { + $success = !$success; + } + + if ($success && $test === 'anyof') + return true; + + if (!$success && $test == 'allof') + return false; + + + } + + // If we got all the way here, it means we haven't been able to + // determine early if the test failed or not. + // + // This implies for 'anyof' that the test failed, and for 'allof' that + // we succeeded. Sounds weird, but makes sense. + return $test === 'allof'; + + } + + /** + * This event is triggered when fetching properties. + * + * This event is scheduled late in the process, after most work for + * propfind has been done. + * + * @param DAV\PropFind $propFind + * @param DAV\INode $node + * @return void + */ + function propFindLate(DAV\PropFind $propFind, DAV\INode $node) { + + // If the request was made using the SOGO connector, we must rewrite + // the content-type property. By default SabreDAV will send back + // text/x-vcard; charset=utf-8, but for SOGO we must strip that last + // part. + if (strpos($this->server->httpRequest->getHeader('User-Agent'), 'Thunderbird') === false) { + return; + } + $contentType = $propFind->get('{DAV:}getcontenttype'); + list($part) = explode(';', $contentType); + if ($part === 'text/x-vcard' || $part === 'text/vcard') { + $propFind->set('{DAV:}getcontenttype', 'text/x-vcard'); + } + + } + + /** + * This method is used to generate HTML output for the + * Sabre\DAV\Browser\Plugin. This allows us to generate an interface users + * can use to create new addressbooks. + * + * @param DAV\INode $node + * @param string $output + * @return bool + */ + function htmlActionsPanel(DAV\INode $node, &$output) { + + if (!$node instanceof AddressBookHome) + return; + + $output .= '
+

Create new address book

+ + +
+
+ +
+ '; + + return false; + + } + + /** + * This event is triggered after GET requests. + * + * This is used to transform data into jCal, if this was requested. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function httpAfterGet(RequestInterface $request, ResponseInterface $response) { + + if (strpos($response->getHeader('Content-Type'), 'text/vcard') === false) { + return; + } + + $target = $this->negotiateVCard($request->getHeader('Accept'), $mimeType); + + $newBody = $this->convertVCard( + $response->getBody(), + $target + ); + + $response->setBody($newBody); + $response->setHeader('Content-Type', $mimeType . '; charset=utf-8'); + $response->setHeader('Content-Length', strlen($newBody)); + + } + + /** + * This helper function performs the content-type negotiation for vcards. + * + * It will return one of the following strings: + * 1. vcard3 + * 2. vcard4 + * 3. jcard + * + * It defaults to vcard3. + * + * @param string $input + * @param string $mimeType + * @return string + */ + protected function negotiateVCard($input, &$mimeType = null) { + + $result = HTTP\Util::negotiate( + $input, + [ + // Most often used mime-type. Version 3 + 'text/x-vcard', + // The correct standard mime-type. Defaults to version 3 as + // well. + 'text/vcard', + // vCard 4 + 'text/vcard; version=4.0', + // vCard 3 + 'text/vcard; version=3.0', + // jCard + 'application/vcard+json', + ] + ); + + $mimeType = $result; + switch ($result) { + + default : + case 'text/x-vcard' : + case 'text/vcard' : + case 'text/vcard; version=3.0' : + $mimeType = 'text/vcard'; + return 'vcard3'; + case 'text/vcard; version=4.0' : + return 'vcard4'; + case 'application/vcard+json' : + return 'jcard'; + + // @codeCoverageIgnoreStart + } + // @codeCoverageIgnoreEnd + + } + + /** + * Converts a vcard blob to a different version, or jcard. + * + * @param string $data + * @param string $target + * @return string + */ + protected function convertVCard($data, $target) { + + $data = VObject\Reader::read($data); + switch ($target) { + default : + case 'vcard3' : + $data = $data->convert(VObject\Document::VCARD30); + $newResult = $data->serialize(); + break; + case 'vcard4' : + $data = $data->convert(VObject\Document::VCARD40); + $newResult = $data->serialize(); + break; + case 'jcard' : + $data = $data->convert(VObject\Document::VCARD40); + $newResult = json_encode($data->jsonSerialize()); + break; + + } + // Destroy circular references to PHP will GC the object. + $data->destroy(); + + return $newResult; + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'carddav'; + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Adds support for CardDAV (rfc6352)', + 'link' => 'http://sabre.io/dav/carddav/', + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php b/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php new file mode 100644 index 000000000..de8b3bb84 --- /dev/null +++ b/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php @@ -0,0 +1,152 @@ +server = $server; + $this->server->on('method:GET', [$this, 'httpGet'], 90); + $server->on('browserButtonActions', function($path, $node, &$actions) { + if ($node instanceof IAddressBook) { + $actions .= ''; + } + }); + } + + /** + * Intercepts GET requests on addressbook urls ending with ?export. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpGet(RequestInterface $request, ResponseInterface $response) { + + $queryParams = $request->getQueryParameters(); + if (!array_key_exists('export', $queryParams)) return; + + $path = $request->getPath(); + + $node = $this->server->tree->getNodeForPath($path); + + if (!($node instanceof IAddressBook)) return; + + $this->server->transactionType = 'get-addressbook-export'; + + // Checking ACL, if available. + if ($aclPlugin = $this->server->getPlugin('acl')) { + $aclPlugin->checkPrivileges($path, '{DAV:}read'); + } + + $response->setHeader('Content-Type', 'text/directory'); + $response->setStatus(200); + + $nodes = $this->server->getPropertiesForPath($path, [ + '{' . Plugin::NS_CARDDAV . '}address-data', + ], 1); + + $response->setBody($this->generateVCF($nodes)); + + // Returning false to break the event chain + return false; + + } + + /** + * Merges all vcard objects, and builds one big vcf export + * + * @param array $nodes + * @return string + */ + function generateVCF(array $nodes) { + + $output = ""; + + foreach ($nodes as $node) { + + if (!isset($node[200]['{' . Plugin::NS_CARDDAV . '}address-data'])) { + continue; + } + $nodeData = $node[200]['{' . Plugin::NS_CARDDAV . '}address-data']; + + // Parsing this node so VObject can clean up the output. + $vcard = VObject\Reader::read($nodeData); + $output .= $vcard->serialize(); + + // Destroy circular references to PHP will GC the object. + $vcard->destroy(); + + } + + return $output; + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using \Sabre\DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'vcf-export'; + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Adds the ability to export CardDAV addressbooks as a single vCard file.', + 'link' => 'http://sabre.io/dav/vcf-export-plugin/', + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php b/vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php new file mode 100644 index 000000000..34028db85 --- /dev/null +++ b/vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php @@ -0,0 +1,59 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $result = [ + 'contentType' => $reader->getAttribute('content-type') ?: 'text/vcard', + 'version' => $reader->getAttribute('version') ?: '3.0', + ]; + + $reader->next(); + return $result; + + } + +} diff --git a/vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php b/vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php new file mode 100644 index 000000000..9646ae3e6 --- /dev/null +++ b/vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php @@ -0,0 +1,89 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $result = [ + 'name' => null, + 'is-not-defined' => false, + 'text-match' => null, + ]; + + $att = $reader->parseAttributes(); + $result['name'] = $att['name']; + + $elems = $reader->parseInnerTree(); + + if (is_array($elems)) foreach ($elems as $elem) { + + switch ($elem['name']) { + + case '{' . Plugin::NS_CARDDAV . '}is-not-defined' : + $result['is-not-defined'] = true; + break; + case '{' . Plugin::NS_CARDDAV . '}text-match' : + $matchType = isset($elem['attributes']['match-type']) ? $elem['attributes']['match-type'] : 'contains'; + + if (!in_array($matchType, ['contains', 'equals', 'starts-with', 'ends-with'])) { + throw new BadRequest('Unknown match-type: ' . $matchType); + } + $result['text-match'] = [ + 'negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes', + 'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;unicode-casemap', + 'value' => $elem['value'], + 'match-type' => $matchType, + ]; + break; + + } + + } + + return $result; + + } + +} diff --git a/vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php b/vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php new file mode 100644 index 000000000..c162da160 --- /dev/null +++ b/vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php @@ -0,0 +1,98 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $result = [ + 'name' => null, + 'test' => 'anyof', + 'is-not-defined' => false, + 'param-filters' => [], + 'text-matches' => [], + ]; + + $att = $reader->parseAttributes(); + $result['name'] = $att['name']; + + if (isset($att['test']) && $att['test'] === 'allof') { + $result['test'] = 'allof'; + } + + $elems = $reader->parseInnerTree(); + + if (is_array($elems)) foreach ($elems as $elem) { + + switch ($elem['name']) { + + case '{' . Plugin::NS_CARDDAV . '}param-filter' : + $result['param-filters'][] = $elem['value']; + break; + case '{' . Plugin::NS_CARDDAV . '}is-not-defined' : + $result['is-not-defined'] = true; + break; + case '{' . Plugin::NS_CARDDAV . '}text-match' : + $matchType = isset($elem['attributes']['match-type']) ? $elem['attributes']['match-type'] : 'contains'; + + if (!in_array($matchType, ['contains', 'equals', 'starts-with', 'ends-with'])) { + throw new BadRequest('Unknown match-type: ' . $matchType); + } + $result['text-matches'][] = [ + 'negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes', + 'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;unicode-casemap', + 'value' => $elem['value'], + 'match-type' => $matchType, + ]; + break; + + } + + } + + return $result; + + } + +} diff --git a/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php b/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php new file mode 100644 index 000000000..6ff57b6e3 --- /dev/null +++ b/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php @@ -0,0 +1,83 @@ + 'text/vcard', 'version' => '3.0'], + ['contentType' => 'text/vcard', 'version' => '4.0'], + ['contentType' => 'application/vcard+json', 'version' => '4.0'], + ]; + } + + $this->supportedData = $supportedData; + + } + + /** + * The xmlSerialize metod is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->supportedData as $supported) { + $writer->startElement('{' . Plugin::NS_CARDDAV . '}address-data-type'); + $writer->writeAttributes([ + 'content-type' => $supported['contentType'], + 'version' => $supported['version'] + ]); + $writer->endElement(); // address-data-type + } + + } + +} diff --git a/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php b/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php new file mode 100644 index 000000000..1fc064900 --- /dev/null +++ b/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php @@ -0,0 +1,47 @@ +writeElement('{urn:ietf:params:xml:ns:carddav}supported-collation', $coll); + } + + } + +} diff --git a/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php b/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php new file mode 100644 index 000000000..c97c5eb4f --- /dev/null +++ b/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php @@ -0,0 +1,113 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elems = $reader->parseInnerTree([ + '{urn:ietf:params:xml:ns:carddav}address-data' => 'Sabre\\CardDAV\\Xml\\Filter\\AddressData', + '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue', + ]); + + $newProps = [ + 'hrefs' => [], + 'properties' => [] + ]; + + foreach ($elems as $elem) { + + switch ($elem['name']) { + + case '{DAV:}prop' : + $newProps['properties'] = array_keys($elem['value']); + if (isset($elem['value']['{' . Plugin::NS_CARDDAV . '}address-data'])) { + $newProps += $elem['value']['{' . Plugin::NS_CARDDAV . '}address-data']; + } + break; + case '{DAV:}href' : + $newProps['hrefs'][] = Uri\resolve($reader->contextUri, $elem['value']); + break; + + } + + } + + $obj = new self(); + foreach ($newProps as $key => $value) { + $obj->$key = $value; + } + return $obj; + + } + +} diff --git a/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php b/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php new file mode 100644 index 000000000..a68ac5800 --- /dev/null +++ b/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php @@ -0,0 +1,192 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elems = (array)$reader->parseInnerTree([ + '{urn:ietf:params:xml:ns:carddav}prop-filter' => 'Sabre\\CardDAV\\Xml\\Filter\\PropFilter', + '{urn:ietf:params:xml:ns:carddav}param-filter' => 'Sabre\\CardDAV\\Xml\\Filter\\ParamFilter', + '{urn:ietf:params:xml:ns:carddav}address-data' => 'Sabre\\CardDAV\\Xml\\Filter\\AddressData', + '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue', + ]); + + $newProps = [ + 'filters' => null, + 'properties' => [], + 'test' => 'anyof', + 'limit' => null, + ]; + + if (!is_array($elems)) $elems = []; + + foreach ($elems as $elem) { + + switch ($elem['name']) { + + case '{DAV:}prop' : + $newProps['properties'] = array_keys($elem['value']); + if (isset($elem['value']['{' . Plugin::NS_CARDDAV . '}address-data'])) { + $newProps += $elem['value']['{' . Plugin::NS_CARDDAV . '}address-data']; + } + break; + case '{' . Plugin::NS_CARDDAV . '}filter' : + + if (!is_null($newProps['filters'])) { + throw new BadRequest('You can only include 1 {' . Plugin::NS_CARDDAV . '}filter element'); + } + if (isset($elem['attributes']['test'])) { + $newProps['test'] = $elem['attributes']['test']; + if ($newProps['test'] !== 'allof' && $newProps['test'] !== 'anyof') { + throw new BadRequest('The "test" attribute must be one of "allof" or "anyof"'); + } + } + + $newProps['filters'] = []; + foreach ((array)$elem['value'] as $subElem) { + if ($subElem['name'] === '{' . Plugin::NS_CARDDAV . '}prop-filter') { + $newProps['filters'][] = $subElem['value']; + } + } + break; + case '{' . Plugin::NS_CARDDAV . '}limit' : + foreach ($elem['value'] as $child) { + if ($child['name'] === '{' . Plugin::NS_CARDDAV . '}nresults') { + $newProps['limit'] = (int)$child['value']; + } + } + break; + + } + + } + + if (is_null($newProps['filters'])) { + /* + * We are supposed to throw this error, but KDE sometimes does not + * include the filter element, and we need to treat it as if no + * filters are supplied + */ + //throw new BadRequest('The {' . Plugin::NS_CARDDAV . '}filter element is required for this request'); + $newProps['filters'] = []; + + } + + $obj = new self(); + foreach ($newProps as $key => $value) { + $obj->$key = $value; + } + + return $obj; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php b/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php new file mode 100644 index 000000000..40a95f8bf --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php @@ -0,0 +1,144 @@ +realm = $realm; + + } + + /** + * When this method is called, the backend must check if authentication was + * successful. + * + * The returned value must be one of the following + * + * [true, "principals/username"] + * [false, "reason for failure"] + * + * If authentication was successful, it's expected that the authentication + * backend returns a so-called principal url. + * + * Examples of a principal url: + * + * principals/admin + * principals/user1 + * principals/users/joe + * principals/uid/123457 + * + * If you don't use WebDAV ACL (RFC3744) we recommend that you simply + * return a string such as: + * + * principals/users/[username] + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array + */ + function check(RequestInterface $request, ResponseInterface $response) { + + $auth = new HTTP\Auth\Basic( + $this->realm, + $request, + $response + ); + + $userpass = $auth->getCredentials(); + if (!$userpass) { + return [false, "No 'Authorization: Basic' header found. Either the client didn't send one, or the server is misconfigured"]; + } + if (!$this->validateUserPass($userpass[0], $userpass[1])) { + return [false, "Username or password was incorrect"]; + } + return [true, $this->principalPrefix . $userpass[0]]; + + } + + /** + * This method is called when a user could not be authenticated, and + * authentication was required for the current request. + * + * This gives you the opportunity to set authentication headers. The 401 + * status code will already be set. + * + * In this case of Basic Auth, this would for example mean that the + * following header needs to be set: + * + * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV'); + * + * Keep in mind that in the case of multiple authentication backends, other + * WWW-Authenticate headers may already have been set, and you'll want to + * append your own WWW-Authenticate header instead of overwriting the + * existing one. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function challenge(RequestInterface $request, ResponseInterface $response) { + + $auth = new HTTP\Auth\Basic( + $this->realm, + $request, + $response + ); + $auth->requireLogin(); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBearer.php b/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBearer.php new file mode 100644 index 000000000..ae7a8a12f --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBearer.php @@ -0,0 +1,138 @@ +realm = $realm; + + } + + /** + * When this method is called, the backend must check if authentication was + * successful. + * + * The returned value must be one of the following + * + * [true, "principals/username"] + * [false, "reason for failure"] + * + * If authentication was successful, it's expected that the authentication + * backend returns a so-called principal url. + * + * Examples of a principal url: + * + * principals/admin + * principals/user1 + * principals/users/joe + * principals/uid/123457 + * + * If you don't use WebDAV ACL (RFC3744) we recommend that you simply + * return a string such as: + * + * principals/users/[username] + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array + */ + function check(RequestInterface $request, ResponseInterface $response) { + + $auth = new HTTP\Auth\Bearer( + $this->realm, + $request, + $response + ); + + $bearerToken = $auth->getToken($request); + if (!$bearerToken) { + return [false, "No 'Authorization: Bearer' header found. Either the client didn't send one, or the server is mis-configured"]; + } + $principalUrl = $this->validateBearerToken($bearerToken); + if (!$principalUrl) { + return [false, "Bearer token was incorrect"]; + } + return [true, $principalUrl]; + + } + + /** + * This method is called when a user could not be authenticated, and + * authentication was required for the current request. + * + * This gives you the opportunity to set authentication headers. The 401 + * status code will already be set. + * + * In this case of Bearer Auth, this would for example mean that the + * following header needs to be set: + * + * $response->addHeader('WWW-Authenticate', 'Bearer realm=SabreDAV'); + * + * Keep in mind that in the case of multiple authentication backends, other + * WWW-Authenticate headers may already have been set, and you'll want to + * append your own WWW-Authenticate header instead of overwriting the + * existing one. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function challenge(RequestInterface $request, ResponseInterface $response) { + + $auth = new HTTP\Auth\Bearer( + $this->realm, + $request, + $response + ); + $auth->requireLogin(); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php b/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php new file mode 100644 index 000000000..0251decc1 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php @@ -0,0 +1,162 @@ +realm = $realm; + + } + + /** + * Returns a users digest hash based on the username and realm. + * + * If the user was not known, null must be returned. + * + * @param string $realm + * @param string $username + * @return string|null + */ + abstract function getDigestHash($realm, $username); + + /** + * When this method is called, the backend must check if authentication was + * successful. + * + * The returned value must be one of the following + * + * [true, "principals/username"] + * [false, "reason for failure"] + * + * If authentication was successful, it's expected that the authentication + * backend returns a so-called principal url. + * + * Examples of a principal url: + * + * principals/admin + * principals/user1 + * principals/users/joe + * principals/uid/123457 + * + * If you don't use WebDAV ACL (RFC3744) we recommend that you simply + * return a string such as: + * + * principals/users/[username] + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array + */ + function check(RequestInterface $request, ResponseInterface $response) { + + $digest = new HTTP\Auth\Digest( + $this->realm, + $request, + $response + ); + $digest->init(); + + $username = $digest->getUsername(); + + // No username was given + if (!$username) { + return [false, "No 'Authorization: Digest' header found. Either the client didn't send one, or the server is misconfigured"]; + } + + $hash = $this->getDigestHash($this->realm, $username); + // If this was false, the user account didn't exist + if ($hash === false || is_null($hash)) { + return [false, "Username or password was incorrect"]; + } + if (!is_string($hash)) { + throw new DAV\Exception('The returned value from getDigestHash must be a string or null'); + } + + // If this was false, the password or part of the hash was incorrect. + if (!$digest->validateA1($hash)) { + return [false, "Username or password was incorrect"]; + } + + return [true, $this->principalPrefix . $username]; + + } + + /** + * This method is called when a user could not be authenticated, and + * authentication was required for the current request. + * + * This gives you the opportunity to set authentication headers. The 401 + * status code will already be set. + * + * In this case of Basic Auth, this would for example mean that the + * following header needs to be set: + * + * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV'); + * + * Keep in mind that in the case of multiple authentication backends, other + * WWW-Authenticate headers may already have been set, and you'll want to + * append your own WWW-Authenticate header instead of overwriting the + * existing one. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function challenge(RequestInterface $request, ResponseInterface $response) { + + $auth = new HTTP\Auth\Digest( + $this->realm, + $request, + $response + ); + $auth->init(); + $auth->requireLogin(); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php b/vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php new file mode 100644 index 000000000..e203d2685 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php @@ -0,0 +1,96 @@ +getRawServerValue('REMOTE_USER'); + if (is_null($remoteUser)) { + $remoteUser = $request->getRawServerValue('REDIRECT_REMOTE_USER'); + } + if (is_null($remoteUser)) { + return [false, 'No REMOTE_USER property was found in the PHP $_SERVER super-global. This likely means your server is not configured correctly']; + } + + return [true, $this->principalPrefix . $remoteUser]; + + } + + /** + * This method is called when a user could not be authenticated, and + * authentication was required for the current request. + * + * This gives you the opportunity to set authentication headers. The 401 + * status code will already be set. + * + * In this case of Basic Auth, this would for example mean that the + * following header needs to be set: + * + * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV'); + * + * Keep in mind that in the case of multiple authentication backends, other + * WWW-Authenticate headers may already have been set, and you'll want to + * append your own WWW-Authenticate header instead of overwriting the + * existing one. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function challenge(RequestInterface $request, ResponseInterface $response) { + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php b/vendor/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php new file mode 100644 index 000000000..0fb2210f4 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php @@ -0,0 +1,70 @@ +addHeader('WWW-Authenticate', 'Basic realm=SabreDAV'); + * + * Keep in mind that in the case of multiple authentication backends, other + * WWW-Authenticate headers may already have been set, and you'll want to + * append your own WWW-Authenticate header instead of overwriting the + * existing one. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function challenge(RequestInterface $request, ResponseInterface $response); + +} diff --git a/vendor/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php b/vendor/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php new file mode 100644 index 000000000..7ad8f48b2 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php @@ -0,0 +1,58 @@ +callBack = $callBack; + + } + + /** + * Validates a username and password + * + * This method should return true or false depending on if login + * succeeded. + * + * @param string $username + * @param string $password + * @return bool + */ + protected function validateUserPass($username, $password) { + + $cb = $this->callBack; + return $cb($username, $password); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Auth/Backend/File.php b/vendor/sabre/dav/lib/DAV/Auth/Backend/File.php new file mode 100644 index 000000000..6756e68df --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Auth/Backend/File.php @@ -0,0 +1,77 @@ +loadFile($filename); + + } + + /** + * Loads an htdigest-formatted file. This method can be called multiple times if + * more than 1 file is used. + * + * @param string $filename + * @return void + */ + function loadFile($filename) { + + foreach (file($filename, FILE_IGNORE_NEW_LINES) as $line) { + + if (substr_count($line, ":") !== 2) + throw new DAV\Exception('Malformed htdigest file. Every line should contain 2 colons'); + + list($username, $realm, $A1) = explode(':', $line); + + if (!preg_match('/^[a-zA-Z0-9]{32}$/', $A1)) + throw new DAV\Exception('Malformed htdigest file. Invalid md5 hash'); + + $this->users[$realm . ':' . $username] = $A1; + + } + + } + + /** + * Returns a users' information + * + * @param string $realm + * @param string $username + * @return string + */ + function getDigestHash($realm, $username) { + + return isset($this->users[$realm . ':' . $username]) ? $this->users[$realm . ':' . $username] : false; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php b/vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php new file mode 100644 index 000000000..76ad89391 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php @@ -0,0 +1,57 @@ +pdo = $pdo; + + } + + /** + * Returns the digest hash for a user. + * + * @param string $realm + * @param string $username + * @return string|null + */ + function getDigestHash($realm, $username) { + + $stmt = $this->pdo->prepare('SELECT digesta1 FROM ' . $this->tableName . ' WHERE username = ?'); + $stmt->execute([$username]); + return $stmt->fetchColumn() ?: null; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Auth/Plugin.php b/vendor/sabre/dav/lib/DAV/Auth/Plugin.php new file mode 100644 index 000000000..818d8a4ad --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Auth/Plugin.php @@ -0,0 +1,213 @@ +addBackend($authBackend); + } + + } + + /** + * Adds an authentication backend to the plugin. + * + * @param Backend\BackendInterface $authBackend + * @return void + */ + function addBackend(Backend\BackendInterface $authBackend) { + + $this->backends[] = $authBackend; + + } + + /** + * Initializes the plugin. This function is automatically called by the server + * + * @param Server $server + * @return void + */ + function initialize(Server $server) { + + $server->on('beforeMethod', [$this, 'beforeMethod'], 10); + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'auth'; + + } + + /** + * Returns the currently logged-in principal. + * + * This will return a string such as: + * + * principals/username + * principals/users/username + * + * This method will return null if nobody is logged in. + * + * @return string|null + */ + function getCurrentPrincipal() { + + return $this->currentPrincipal; + + } + + /** + * Returns the current username. + * + * This method is deprecated and is only kept for backwards compatibility + * purposes. Please switch to getCurrentPrincipal(). + * + * @deprecated Will be removed in a future version! + * @return string|null + */ + function getCurrentUser() { + + // We just do a 'basename' on the principal to give back a sane value + // here. + list(, $userName) = URLUtil::splitPath( + $this->getCurrentPrincipal() + ); + + return $userName; + + } + + /** + * This method is called before any HTTP method and forces users to be authenticated + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function beforeMethod(RequestInterface $request, ResponseInterface $response) { + + if ($this->currentPrincipal) { + + // We already have authentication information. This means that the + // event has already fired earlier, and is now likely fired for a + // sub-request. + // + // We don't want to authenticate users twice, so we simply don't do + // anything here. See Issue #700 for additional reasoning. + // + // This is not a perfect solution, but will be fixed once the + // "currently authenticated principal" is information that's not + // not associated with the plugin, but rather per-request. + // + // See issue #580 for more information about that. + return; + + } + if (!$this->backends) { + throw new \Sabre\DAV\Exception('No authentication backends were configured on this server.'); + } + $reasons = []; + foreach ($this->backends as $backend) { + + $result = $backend->check( + $request, + $response + ); + + if (!is_array($result) || count($result) !== 2 || !is_bool($result[0]) || !is_string($result[1])) { + throw new \Sabre\DAV\Exception('The authentication backend did not return a correct value from the check() method.'); + } + + if ($result[0]) { + $this->currentPrincipal = $result[1]; + // Exit early + return; + } + $reasons[] = $result[1]; + + } + + // If we got here, it means that no authentication backend was + // successful in authenticating the user. + $this->currentPrincipal = null; + + foreach ($this->backends as $backend) { + $backend->challenge($request, $response); + } + throw new NotAuthenticated(implode(', ', $reasons)); + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Generic authentication plugin', + 'link' => 'http://sabre.io/dav/authentication/', + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Browser/GuessContentType.php b/vendor/sabre/dav/lib/DAV/Browser/GuessContentType.php new file mode 100644 index 000000000..01cddc230 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/GuessContentType.php @@ -0,0 +1,101 @@ + 'image/jpeg', + 'gif' => 'image/gif', + 'png' => 'image/png', + + // groupware + 'ics' => 'text/calendar', + 'vcf' => 'text/vcard', + + // text + 'txt' => 'text/plain', + + ]; + + /** + * Initializes the plugin + * + * @param DAV\Server $server + * @return void + */ + function initialize(DAV\Server $server) { + + // Using a relatively low priority (200) to allow other extensions + // to set the content-type first. + $server->on('propFind', [$this, 'propFind'], 200); + + } + + /** + * Our PROPFIND handler + * + * Here we set a contenttype, if the node didn't already have one. + * + * @param PropFind $propFind + * @param INode $node + * @return void + */ + function propFind(PropFind $propFind, INode $node) { + + $propFind->handle('{DAV:}getcontenttype', function() use ($propFind) { + + list(, $fileName) = URLUtil::splitPath($propFind->getPath()); + return $this->getContentType($fileName); + + }); + + } + + /** + * Simple method to return the contenttype + * + * @param string $fileName + * @return string + */ + protected function getContentType($fileName) { + + // Just grabbing the extension + $extension = strtolower(substr($fileName, strrpos($fileName, '.') + 1)); + if (isset($this->extensionMap[$extension])) { + return $this->extensionMap[$extension]; + } + return 'application/octet-stream'; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php b/vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php new file mode 100644 index 000000000..f4be6b348 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php @@ -0,0 +1,34 @@ +baseUri = $baseUri; + $this->namespaceMap = $namespaceMap; + + } + + /** + * Generates a 'full' url based on a relative one. + * + * For relative urls, the base of the application is taken as the reference + * url, not the 'current url of the current request'. + * + * Absolute urls are left alone. + * + * @param string $path + * @return string + */ + function fullUrl($path) { + + return Uri\resolve($this->baseUri, $path); + + } + + /** + * Escape string for HTML output. + * + * @param string $input + * @return string + */ + function h($input) { + + return htmlspecialchars($input, ENT_COMPAT, 'UTF-8'); + + } + + /** + * Generates a full -tag. + * + * Url is automatically expanded. If label is not specified, we re-use the + * url. + * + * @param string $url + * @param string $label + * @return string + */ + function link($url, $label = null) { + + $url = $this->h($this->fullUrl($url)); + return '' . ($label ? $this->h($label) : $url) . ''; + + } + + /** + * This method takes an xml element in clark-notation, and turns it into a + * shortened version with a prefix, if it was a known namespace. + * + * @param string $element + * @return string + */ + function xmlName($element) { + + list($ns, $localName) = XmlService::parseClarkNotation($element); + if (isset($this->namespaceMap[$ns])) { + $propName = $this->namespaceMap[$ns] . ':' . $localName; + } else { + $propName = $element; + } + return "h($element) . "\">" . $this->h($propName) . ""; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php b/vendor/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php new file mode 100644 index 000000000..38ee63bcd --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php @@ -0,0 +1,60 @@ +server = $server; + $this->server->on('method:GET', [$this, 'httpGet'], 90); + } + + /** + * This method intercepts GET requests to non-files, and changes it into an HTTP PROPFIND request + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpGet(RequestInterface $request, ResponseInterface $response) { + + $node = $this->server->tree->getNodeForPath($request->getPath()); + if ($node instanceof DAV\IFile) return; + + $subRequest = clone $request; + $subRequest->setMethod('PROPFIND'); + + $this->server->invokeMethod($subRequest, $response); + return false; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Browser/Plugin.php b/vendor/sabre/dav/lib/DAV/Browser/Plugin.php new file mode 100644 index 000000000..07ca6c3e5 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/Plugin.php @@ -0,0 +1,797 @@ +enablePost = $enablePost; + + } + + /** + * Initializes the plugin and subscribes to events + * + * @param DAV\Server $server + * @return void + */ + function initialize(DAV\Server $server) { + + $this->server = $server; + $this->server->on('method:GET', [$this, 'httpGetEarly'], 90); + $this->server->on('method:GET', [$this, 'httpGet'], 200); + $this->server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel'], 200); + if ($this->enablePost) $this->server->on('method:POST', [$this, 'httpPOST']); + } + + /** + * This method intercepts GET requests that have ?sabreAction=info + * appended to the URL + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpGetEarly(RequestInterface $request, ResponseInterface $response) { + + $params = $request->getQueryParameters(); + if (isset($params['sabreAction']) && $params['sabreAction'] === 'info') { + return $this->httpGet($request, $response); + } + + } + + /** + * This method intercepts GET requests to collections and returns the html + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpGet(RequestInterface $request, ResponseInterface $response) { + + // We're not using straight-up $_GET, because we want everything to be + // unit testable. + $getVars = $request->getQueryParameters(); + + // CSP headers + $this->server->httpResponse->setHeader('Content-Security-Policy', "img-src 'self'; style-src 'self';"); + + $sabreAction = isset($getVars['sabreAction']) ? $getVars['sabreAction'] : null; + + switch ($sabreAction) { + + case 'asset' : + // Asset handling, such as images + $this->serveAsset(isset($getVars['assetName']) ? $getVars['assetName'] : null); + return false; + default : + case 'info' : + try { + $this->server->tree->getNodeForPath($request->getPath()); + } catch (DAV\Exception\NotFound $e) { + // We're simply stopping when the file isn't found to not interfere + // with other plugins. + return; + } + + $response->setStatus(200); + $response->setHeader('Content-Type', 'text/html; charset=utf-8'); + + $response->setBody( + $this->generateDirectoryIndex($request->getPath()) + ); + + return false; + + case 'plugins' : + $response->setStatus(200); + $response->setHeader('Content-Type', 'text/html; charset=utf-8'); + + $response->setBody( + $this->generatePluginListing() + ); + + return false; + + } + + } + + /** + * Handles POST requests for tree operations. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpPOST(RequestInterface $request, ResponseInterface $response) { + + $contentType = $request->getHeader('Content-Type'); + list($contentType) = explode(';', $contentType); + if ($contentType !== 'application/x-www-form-urlencoded' && + $contentType !== 'multipart/form-data') { + return; + } + $postVars = $request->getPostData(); + + if (!isset($postVars['sabreAction'])) + return; + + $uri = $request->getPath(); + + if ($this->server->emit('onBrowserPostAction', [$uri, $postVars['sabreAction'], $postVars])) { + + switch ($postVars['sabreAction']) { + + case 'mkcol' : + if (isset($postVars['name']) && trim($postVars['name'])) { + // Using basename() because we won't allow slashes + list(, $folderName) = URLUtil::splitPath(trim($postVars['name'])); + + if (isset($postVars['resourceType'])) { + $resourceType = explode(',', $postVars['resourceType']); + } else { + $resourceType = ['{DAV:}collection']; + } + + $properties = []; + foreach ($postVars as $varName => $varValue) { + // Any _POST variable in clark notation is treated + // like a property. + if ($varName[0] === '{') { + // PHP will convert any dots to underscores. + // This leaves us with no way to differentiate + // the two. + // Therefore we replace the string *DOT* with a + // real dot. * is not allowed in uris so we + // should be good. + $varName = str_replace('*DOT*', '.', $varName); + $properties[$varName] = $varValue; + } + } + + $mkCol = new MkCol( + $resourceType, + $properties + ); + $this->server->createCollection($uri . '/' . $folderName, $mkCol); + } + break; + + // @codeCoverageIgnoreStart + case 'put' : + + if ($_FILES) $file = current($_FILES); + else break; + + list(, $newName) = URLUtil::splitPath(trim($file['name'])); + if (isset($postVars['name']) && trim($postVars['name'])) + $newName = trim($postVars['name']); + + // Making sure we only have a 'basename' component + list(, $newName) = URLUtil::splitPath($newName); + + if (is_uploaded_file($file['tmp_name'])) { + $this->server->createFile($uri . '/' . $newName, fopen($file['tmp_name'], 'r')); + } + break; + // @codeCoverageIgnoreEnd + + } + + } + $response->setHeader('Location', $request->getUrl()); + $response->setStatus(302); + return false; + + } + + /** + * Escapes a string for html. + * + * @param string $value + * @return string + */ + function escapeHTML($value) { + + return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); + + } + + /** + * Generates the html directory index for a given url + * + * @param string $path + * @return string + */ + function generateDirectoryIndex($path) { + + $html = $this->generateHeader($path ? $path : '/', $path); + + $node = $this->server->tree->getNodeForPath($path); + if ($node instanceof DAV\ICollection) { + + $html .= "

Nodes

\n"; + $html .= ""; + + $subNodes = $this->server->getPropertiesForChildren($path, [ + '{DAV:}displayname', + '{DAV:}resourcetype', + '{DAV:}getcontenttype', + '{DAV:}getcontentlength', + '{DAV:}getlastmodified', + ]); + + foreach ($subNodes as $subPath => $subProps) { + + $subNode = $this->server->tree->getNodeForPath($subPath); + $fullPath = $this->server->getBaseUri() . URLUtil::encodePath($subPath); + list(, $displayPath) = URLUtil::splitPath($subPath); + + $subNodes[$subPath]['subNode'] = $subNode; + $subNodes[$subPath]['fullPath'] = $fullPath; + $subNodes[$subPath]['displayPath'] = $displayPath; + } + uasort($subNodes, [$this, 'compareNodes']); + + foreach ($subNodes as $subProps) { + $type = [ + 'string' => 'Unknown', + 'icon' => 'cog', + ]; + if (isset($subProps['{DAV:}resourcetype'])) { + $type = $this->mapResourceType($subProps['{DAV:}resourcetype']->getValue(), $subProps['subNode']); + } + + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + + $buttonActions = ''; + if ($subProps['subNode'] instanceof DAV\IFile) { + $buttonActions = ''; + } + $this->server->emit('browserButtonActions', [$subProps['fullPath'], $subProps['subNode'], &$buttonActions]); + + $html .= ''; + $html .= ''; + } + + $html .= '
' . $this->escapeHTML($subProps['displayPath']) . '' . $this->escapeHTML($type['string']) . ''; + if (isset($subProps['{DAV:}getcontentlength'])) { + $html .= $this->escapeHTML($subProps['{DAV:}getcontentlength'] . ' bytes'); + } + $html .= ''; + if (isset($subProps['{DAV:}getlastmodified'])) { + $lastMod = $subProps['{DAV:}getlastmodified']->getTime(); + $html .= $this->escapeHTML($lastMod->format('F j, Y, g:i a')); + } + $html .= '' . $buttonActions . '
'; + + } + + $html .= "
"; + $html .= "

Properties

"; + $html .= ""; + + // Allprops request + $propFind = new PropFindAll($path); + $properties = $this->server->getPropertiesByNode($propFind, $node); + + $properties = $propFind->getResultForMultiStatus()[200]; + + foreach ($properties as $propName => $propValue) { + if (!in_array($propName, $this->uninterestingProperties)) { + $html .= $this->drawPropertyRow($propName, $propValue); + } + + } + + + $html .= "
"; + $html .= "
"; + + /* Start of generating actions */ + + $output = ''; + if ($this->enablePost) { + $this->server->emit('onHTMLActionsPanel', [$node, &$output]); + } + + if ($output) { + + $html .= "

Actions

"; + $html .= "
\n"; + $html .= $output; + $html .= "
\n"; + $html .= "
\n"; + } + + $html .= $this->generateFooter(); + + $this->server->httpResponse->setHeader('Content-Security-Policy', "img-src 'self'; style-src 'self';"); + + return $html; + + } + + /** + * Generates the 'plugins' page. + * + * @return string + */ + function generatePluginListing() { + + $html = $this->generateHeader('Plugins'); + + $html .= "

Plugins

"; + $html .= ""; + foreach ($this->server->getPlugins() as $plugin) { + $info = $plugin->getPluginInfo(); + $html .= ''; + $html .= ''; + $html .= ''; + } + $html .= "
' . $info['name'] . '' . $info['description'] . ''; + if (isset($info['link']) && $info['link']) { + $html .= ''; + } + $html .= '
"; + $html .= "
"; + + /* Start of generating actions */ + + $html .= $this->generateFooter(); + + return $html; + + } + + /** + * Generates the first block of HTML, including the tag and page + * header. + * + * Returns footer. + * + * @param string $title + * @param string $path + * @return void + */ + function generateHeader($title, $path = null) { + + $version = DAV\Version::VERSION; + + $vars = [ + 'title' => $this->escapeHTML($title), + 'favicon' => $this->escapeHTML($this->getAssetUrl('favicon.ico')), + 'style' => $this->escapeHTML($this->getAssetUrl('sabredav.css')), + 'iconstyle' => $this->escapeHTML($this->getAssetUrl('openiconic/open-iconic.css')), + 'logo' => $this->escapeHTML($this->getAssetUrl('sabredav.png')), + 'baseUrl' => $this->server->getBaseUri(), + ]; + + $html = << + + + $vars[title] - sabre/dav $version + + + + + + +
+ +
+ + "; + + return $html; + + } + + /** + * Generates the page footer. + * + * Returns html. + * + * @return string + */ + function generateFooter() { + + $version = DAV\Version::VERSION; + return <<Generated by SabreDAV $version (c)2007-2015 http://sabre.io/ + + +HTML; + + } + + /** + * This method is used to generate the 'actions panel' output for + * collections. + * + * This specifically generates the interfaces for creating new files, and + * creating new directories. + * + * @param DAV\INode $node + * @param mixed $output + * @return void + */ + function htmlActionsPanel(DAV\INode $node, &$output) { + + if (!$node instanceof DAV\ICollection) + return; + + // We also know fairly certain that if an object is a non-extended + // SimpleCollection, we won't need to show the panel either. + if (get_class($node) === 'Sabre\\DAV\\SimpleCollection') + return; + + ob_start(); + echo '
+

Create new folder

+ +
+ +
+
+

Upload file

+ +
+
+ +
+ '; + + $output .= ob_get_clean(); + + } + + /** + * This method takes a path/name of an asset and turns it into url + * suiteable for http access. + * + * @param string $assetName + * @return string + */ + protected function getAssetUrl($assetName) { + + return $this->server->getBaseUri() . '?sabreAction=asset&assetName=' . urlencode($assetName); + + } + + /** + * This method returns a local pathname to an asset. + * + * @param string $assetName + * @return string + * @throws DAV\Exception\NotFound + */ + protected function getLocalAssetPath($assetName) { + + $assetDir = __DIR__ . '/assets/'; + $path = $assetDir . $assetName; + + // Making sure people aren't trying to escape from the base path. + $path = str_replace('\\', '/', $path); + if (strpos($path, '/../') !== false || strrchr($path, '/') === '/..') { + throw new DAV\Exception\NotFound('Path does not exist, or escaping from the base path was detected'); + } + if (strpos(realpath($path), realpath($assetDir)) === 0 && file_exists($path)) { + return $path; + } + throw new DAV\Exception\NotFound('Path does not exist, or escaping from the base path was detected'); + } + + /** + * This method reads an asset from disk and generates a full http response. + * + * @param string $assetName + * @return void + */ + protected function serveAsset($assetName) { + + $assetPath = $this->getLocalAssetPath($assetName); + + // Rudimentary mime type detection + $mime = 'application/octet-stream'; + $map = [ + 'ico' => 'image/vnd.microsoft.icon', + 'png' => 'image/png', + 'css' => 'text/css', + ]; + + $ext = substr($assetName, strrpos($assetName, '.') + 1); + if (isset($map[$ext])) { + $mime = $map[$ext]; + } + + $this->server->httpResponse->setHeader('Content-Type', $mime); + $this->server->httpResponse->setHeader('Content-Length', filesize($assetPath)); + $this->server->httpResponse->setHeader('Cache-Control', 'public, max-age=1209600'); + $this->server->httpResponse->setStatus(200); + $this->server->httpResponse->setBody(fopen($assetPath, 'r')); + + } + + /** + * Sort helper function: compares two directory entries based on type and + * display name. Collections sort above other types. + * + * @param array $a + * @param array $b + * @return int + */ + protected function compareNodes($a, $b) { + + $typeA = (isset($a['{DAV:}resourcetype'])) + ? (in_array('{DAV:}collection', $a['{DAV:}resourcetype']->getValue())) + : false; + + $typeB = (isset($b['{DAV:}resourcetype'])) + ? (in_array('{DAV:}collection', $b['{DAV:}resourcetype']->getValue())) + : false; + + // If same type, sort alphabetically by filename: + if ($typeA === $typeB) { + return strnatcasecmp($a['displayPath'], $b['displayPath']); + } + return (($typeA < $typeB) ? 1 : -1); + + } + + /** + * Maps a resource type to a human-readable string and icon. + * + * @param array $resourceTypes + * @param INode $node + * @return array + */ + private function mapResourceType(array $resourceTypes, $node) { + + if (!$resourceTypes) { + if ($node instanceof DAV\IFile) { + return [ + 'string' => 'File', + 'icon' => 'file', + ]; + } else { + return [ + 'string' => 'Unknown', + 'icon' => 'cog', + ]; + } + } + + $types = [ + '{http://calendarserver.org/ns/}calendar-proxy-write' => [ + 'string' => 'Proxy-Write', + 'icon' => 'people', + ], + '{http://calendarserver.org/ns/}calendar-proxy-read' => [ + 'string' => 'Proxy-Read', + 'icon' => 'people', + ], + '{urn:ietf:params:xml:ns:caldav}schedule-outbox' => [ + 'string' => 'Outbox', + 'icon' => 'inbox', + ], + '{urn:ietf:params:xml:ns:caldav}schedule-inbox' => [ + 'string' => 'Inbox', + 'icon' => 'inbox', + ], + '{urn:ietf:params:xml:ns:caldav}calendar' => [ + 'string' => 'Calendar', + 'icon' => 'calendar', + ], + '{http://calendarserver.org/ns/}shared-owner' => [ + 'string' => 'Shared', + 'icon' => 'calendar', + ], + '{http://calendarserver.org/ns/}subscribed' => [ + 'string' => 'Subscription', + 'icon' => 'calendar', + ], + '{urn:ietf:params:xml:ns:carddav}directory' => [ + 'string' => 'Directory', + 'icon' => 'globe', + ], + '{urn:ietf:params:xml:ns:carddav}addressbook' => [ + 'string' => 'Address book', + 'icon' => 'book', + ], + '{DAV:}principal' => [ + 'string' => 'Principal', + 'icon' => 'person', + ], + '{DAV:}collection' => [ + 'string' => 'Collection', + 'icon' => 'folder', + ], + ]; + + $info = [ + 'string' => [], + 'icon' => 'cog', + ]; + foreach ($resourceTypes as $k => $resourceType) { + if (isset($types[$resourceType])) { + $info['string'][] = $types[$resourceType]['string']; + } else { + $info['string'][] = $resourceType; + } + } + foreach ($types as $key => $resourceInfo) { + if (in_array($key, $resourceTypes)) { + $info['icon'] = $resourceInfo['icon']; + break; + } + } + $info['string'] = implode(', ', $info['string']); + + return $info; + + } + + /** + * Draws a table row for a property + * + * @param string $name + * @param mixed $value + * @return string + */ + private function drawPropertyRow($name, $value) { + + $html = new HtmlOutputHelper( + $this->server->getBaseUri(), + $this->server->xml->namespaceMap + ); + + return "" . $html->xmlName($name) . "" . $this->drawPropertyValue($html, $value) . ""; + + } + + /** + * Draws a table row for a property + * + * @param HtmlOutputHelper $html + * @param mixed $value + * @return string + */ + private function drawPropertyValue($html, $value) { + + if (is_scalar($value)) { + return $html->h($value); + } elseif ($value instanceof HtmlOutput) { + return $value->toHtml($html); + } elseif ($value instanceof \Sabre\Xml\XmlSerializable) { + + // There's no default html output for this property, we're going + // to output the actual xml serialization instead. + $xml = $this->server->xml->write('{DAV:}root', $value, $this->server->getBaseUri()); + // removing first and last line, as they contain our root + // element. + $xml = explode("\n", $xml); + $xml = array_slice($xml, 2, -2); + return "
" . $html->h(implode("\n", $xml)) . "
"; + + } else { + return "unknown"; + } + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins; + * using \Sabre\DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'browser'; + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Generates HTML indexes and debug information for your sabre/dav server', + 'link' => 'http://sabre.io/dav/browser-plugin/', + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Browser/PropFindAll.php b/vendor/sabre/dav/lib/DAV/Browser/PropFindAll.php new file mode 100644 index 000000000..1ac439672 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/PropFindAll.php @@ -0,0 +1,132 @@ +handle('{DAV:}displayname', function() { + * return 'hello'; + * }); + * + * Note that handle will only work the first time. If null is returned, the + * value is ignored. + * + * It's also possible to not pass a callback, but immediately pass a value + * + * @param string $propertyName + * @param mixed $valueOrCallBack + * @return void + */ + function handle($propertyName, $valueOrCallBack) { + + if (is_callable($valueOrCallBack)) { + $value = $valueOrCallBack(); + } else { + $value = $valueOrCallBack; + } + if (!is_null($value)) { + $this->result[$propertyName] = [200, $value]; + } + + } + + /** + * Sets the value of the property + * + * If status is not supplied, the status will default to 200 for non-null + * properties, and 404 for null properties. + * + * @param string $propertyName + * @param mixed $value + * @param int $status + * @return void + */ + function set($propertyName, $value, $status = null) { + + if (is_null($status)) { + $status = is_null($value) ? 404 : 200; + } + $this->result[$propertyName] = [$status, $value]; + + } + + /** + * Returns the current value for a property. + * + * @param string $propertyName + * @return mixed + */ + function get($propertyName) { + + return isset($this->result[$propertyName]) ? $this->result[$propertyName][1] : null; + + } + + /** + * Returns the current status code for a property name. + * + * If the property does not appear in the list of requested properties, + * null will be returned. + * + * @param string $propertyName + * @return int|null + */ + function getStatus($propertyName) { + + return isset($this->result[$propertyName]) ? $this->result[$propertyName][0] : 404; + + } + + /** + * Returns all propertynames that have a 404 status, and thus don't have a + * value yet. + * + * @return array + */ + function get404Properties() { + + $result = []; + foreach ($this->result as $propertyName => $stuff) { + if ($stuff[0] === 404) { + $result[] = $propertyName; + } + } + // If there's nothing in this list, we're adding one fictional item. + if (!$result) { + $result[] = '{http://sabredav.org/ns}idk'; + } + return $result; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/ICON-LICENSE b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/ICON-LICENSE new file mode 100644 index 000000000..2199f4a69 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/ICON-LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Waybury + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.css b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.css new file mode 100644 index 000000000..e74867400 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.css @@ -0,0 +1,510 @@ +@font-face { + font-family: 'Icons'; + src: url('?sabreAction=asset&assetName=openiconic/open-iconic.eot'); + src: url('?sabreAction=asset&assetName=openiconic/open-iconic.eot?#iconic-sm') format('embedded-opentype'), url('?sabreAction=asset&assetName=openiconic/open-iconic.woff') format('woff'), url('?sabreAction=asset&assetName=openiconic/open-iconic.ttf') format('truetype'), url('?sabreAction=asset&assetName=openiconic/open-iconic.otf') format('opentype'), url('?sabreAction=asset&assetName=openiconic/open-iconic.svg#iconic-sm') format('svg'); + font-weight: normal; + font-style: normal; +} + +.oi[data-glyph].oi-text-replace { + font-size: 0; + line-height: 0; +} + +.oi[data-glyph].oi-text-replace:before { + width: 1em; + text-align: center; +} + +.oi[data-glyph]:before { + font-family: 'Icons'; + display: inline-block; + speak: none; + line-height: 1; + vertical-align: baseline; + font-weight: normal; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.oi[data-glyph]:empty:before { + width: 1em; + text-align: center; + box-sizing: content-box; +} + +.oi[data-glyph].oi-align-left:before { + text-align: left; +} + +.oi[data-glyph].oi-align-right:before { + text-align: right; +} + +.oi[data-glyph].oi-align-center:before { + text-align: center; +} + +.oi[data-glyph].oi-flip-horizontal:before { + -webkit-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + transform: scale(-1, 1); +} +.oi[data-glyph].oi-flip-vertical:before { + -webkit-transform: scale(1, -1); + -ms-transform: scale(-1, 1); + transform: scale(1, -1); +} +.oi[data-glyph].oi-flip-horizontal-vertical:before { + -webkit-transform: scale(-1, -1); + -ms-transform: scale(-1, 1); + transform: scale(-1, -1); +} + + +.oi[data-glyph=account-login]:before { content:'\e000'; } + +.oi[data-glyph=account-logout]:before { content:'\e001'; } + +.oi[data-glyph=action-redo]:before { content:'\e002'; } + +.oi[data-glyph=action-undo]:before { content:'\e003'; } + +.oi[data-glyph=align-center]:before { content:'\e004'; } + +.oi[data-glyph=align-left]:before { content:'\e005'; } + +.oi[data-glyph=align-right]:before { content:'\e006'; } + +.oi[data-glyph=aperture]:before { content:'\e007'; } + +.oi[data-glyph=arrow-bottom]:before { content:'\e008'; } + +.oi[data-glyph=arrow-circle-bottom]:before { content:'\e009'; } + +.oi[data-glyph=arrow-circle-left]:before { content:'\e00a'; } + +.oi[data-glyph=arrow-circle-right]:before { content:'\e00b'; } + +.oi[data-glyph=arrow-circle-top]:before { content:'\e00c'; } + +.oi[data-glyph=arrow-left]:before { content:'\e00d'; } + +.oi[data-glyph=arrow-right]:before { content:'\e00e'; } + +.oi[data-glyph=arrow-thick-bottom]:before { content:'\e00f'; } + +.oi[data-glyph=arrow-thick-left]:before { content:'\e010'; } + +.oi[data-glyph=arrow-thick-right]:before { content:'\e011'; } + +.oi[data-glyph=arrow-thick-top]:before { content:'\e012'; } + +.oi[data-glyph=arrow-top]:before { content:'\e013'; } + +.oi[data-glyph=audio-spectrum]:before { content:'\e014'; } + +.oi[data-glyph=audio]:before { content:'\e015'; } + +.oi[data-glyph=badge]:before { content:'\e016'; } + +.oi[data-glyph=ban]:before { content:'\e017'; } + +.oi[data-glyph=bar-chart]:before { content:'\e018'; } + +.oi[data-glyph=basket]:before { content:'\e019'; } + +.oi[data-glyph=battery-empty]:before { content:'\e01a'; } + +.oi[data-glyph=battery-full]:before { content:'\e01b'; } + +.oi[data-glyph=beaker]:before { content:'\e01c'; } + +.oi[data-glyph=bell]:before { content:'\e01d'; } + +.oi[data-glyph=bluetooth]:before { content:'\e01e'; } + +.oi[data-glyph=bold]:before { content:'\e01f'; } + +.oi[data-glyph=bolt]:before { content:'\e020'; } + +.oi[data-glyph=book]:before { content:'\e021'; } + +.oi[data-glyph=bookmark]:before { content:'\e022'; } + +.oi[data-glyph=box]:before { content:'\e023'; } + +.oi[data-glyph=briefcase]:before { content:'\e024'; } + +.oi[data-glyph=british-pound]:before { content:'\e025'; } + +.oi[data-glyph=browser]:before { content:'\e026'; } + +.oi[data-glyph=brush]:before { content:'\e027'; } + +.oi[data-glyph=bug]:before { content:'\e028'; } + +.oi[data-glyph=bullhorn]:before { content:'\e029'; } + +.oi[data-glyph=calculator]:before { content:'\e02a'; } + +.oi[data-glyph=calendar]:before { content:'\e02b'; } + +.oi[data-glyph=camera-slr]:before { content:'\e02c'; } + +.oi[data-glyph=caret-bottom]:before { content:'\e02d'; } + +.oi[data-glyph=caret-left]:before { content:'\e02e'; } + +.oi[data-glyph=caret-right]:before { content:'\e02f'; } + +.oi[data-glyph=caret-top]:before { content:'\e030'; } + +.oi[data-glyph=cart]:before { content:'\e031'; } + +.oi[data-glyph=chat]:before { content:'\e032'; } + +.oi[data-glyph=check]:before { content:'\e033'; } + +.oi[data-glyph=chevron-bottom]:before { content:'\e034'; } + +.oi[data-glyph=chevron-left]:before { content:'\e035'; } + +.oi[data-glyph=chevron-right]:before { content:'\e036'; } + +.oi[data-glyph=chevron-top]:before { content:'\e037'; } + +.oi[data-glyph=circle-check]:before { content:'\e038'; } + +.oi[data-glyph=circle-x]:before { content:'\e039'; } + +.oi[data-glyph=clipboard]:before { content:'\e03a'; } + +.oi[data-glyph=clock]:before { content:'\e03b'; } + +.oi[data-glyph=cloud-download]:before { content:'\e03c'; } + +.oi[data-glyph=cloud-upload]:before { content:'\e03d'; } + +.oi[data-glyph=cloud]:before { content:'\e03e'; } + +.oi[data-glyph=cloudy]:before { content:'\e03f'; } + +.oi[data-glyph=code]:before { content:'\e040'; } + +.oi[data-glyph=cog]:before { content:'\e041'; } + +.oi[data-glyph=collapse-down]:before { content:'\e042'; } + +.oi[data-glyph=collapse-left]:before { content:'\e043'; } + +.oi[data-glyph=collapse-right]:before { content:'\e044'; } + +.oi[data-glyph=collapse-up]:before { content:'\e045'; } + +.oi[data-glyph=command]:before { content:'\e046'; } + +.oi[data-glyph=comment-square]:before { content:'\e047'; } + +.oi[data-glyph=compass]:before { content:'\e048'; } + +.oi[data-glyph=contrast]:before { content:'\e049'; } + +.oi[data-glyph=copywriting]:before { content:'\e04a'; } + +.oi[data-glyph=credit-card]:before { content:'\e04b'; } + +.oi[data-glyph=crop]:before { content:'\e04c'; } + +.oi[data-glyph=dashboard]:before { content:'\e04d'; } + +.oi[data-glyph=data-transfer-download]:before { content:'\e04e'; } + +.oi[data-glyph=data-transfer-upload]:before { content:'\e04f'; } + +.oi[data-glyph=delete]:before { content:'\e050'; } + +.oi[data-glyph=dial]:before { content:'\e051'; } + +.oi[data-glyph=document]:before { content:'\e052'; } + +.oi[data-glyph=dollar]:before { content:'\e053'; } + +.oi[data-glyph=double-quote-sans-left]:before { content:'\e054'; } + +.oi[data-glyph=double-quote-sans-right]:before { content:'\e055'; } + +.oi[data-glyph=double-quote-serif-left]:before { content:'\e056'; } + +.oi[data-glyph=double-quote-serif-right]:before { content:'\e057'; } + +.oi[data-glyph=droplet]:before { content:'\e058'; } + +.oi[data-glyph=eject]:before { content:'\e059'; } + +.oi[data-glyph=elevator]:before { content:'\e05a'; } + +.oi[data-glyph=ellipses]:before { content:'\e05b'; } + +.oi[data-glyph=envelope-closed]:before { content:'\e05c'; } + +.oi[data-glyph=envelope-open]:before { content:'\e05d'; } + +.oi[data-glyph=euro]:before { content:'\e05e'; } + +.oi[data-glyph=excerpt]:before { content:'\e05f'; } + +.oi[data-glyph=expand-down]:before { content:'\e060'; } + +.oi[data-glyph=expand-left]:before { content:'\e061'; } + +.oi[data-glyph=expand-right]:before { content:'\e062'; } + +.oi[data-glyph=expand-up]:before { content:'\e063'; } + +.oi[data-glyph=external-link]:before { content:'\e064'; } + +.oi[data-glyph=eye]:before { content:'\e065'; } + +.oi[data-glyph=eyedropper]:before { content:'\e066'; } + +.oi[data-glyph=file]:before { content:'\e067'; } + +.oi[data-glyph=fire]:before { content:'\e068'; } + +.oi[data-glyph=flag]:before { content:'\e069'; } + +.oi[data-glyph=flash]:before { content:'\e06a'; } + +.oi[data-glyph=folder]:before { content:'\e06b'; } + +.oi[data-glyph=fork]:before { content:'\e06c'; } + +.oi[data-glyph=fullscreen-enter]:before { content:'\e06d'; } + +.oi[data-glyph=fullscreen-exit]:before { content:'\e06e'; } + +.oi[data-glyph=globe]:before { content:'\e06f'; } + +.oi[data-glyph=graph]:before { content:'\e070'; } + +.oi[data-glyph=grid-four-up]:before { content:'\e071'; } + +.oi[data-glyph=grid-three-up]:before { content:'\e072'; } + +.oi[data-glyph=grid-two-up]:before { content:'\e073'; } + +.oi[data-glyph=hard-drive]:before { content:'\e074'; } + +.oi[data-glyph=header]:before { content:'\e075'; } + +.oi[data-glyph=headphones]:before { content:'\e076'; } + +.oi[data-glyph=heart]:before { content:'\e077'; } + +.oi[data-glyph=home]:before { content:'\e078'; } + +.oi[data-glyph=image]:before { content:'\e079'; } + +.oi[data-glyph=inbox]:before { content:'\e07a'; } + +.oi[data-glyph=infinity]:before { content:'\e07b'; } + +.oi[data-glyph=info]:before { content:'\e07c'; } + +.oi[data-glyph=italic]:before { content:'\e07d'; } + +.oi[data-glyph=justify-center]:before { content:'\e07e'; } + +.oi[data-glyph=justify-left]:before { content:'\e07f'; } + +.oi[data-glyph=justify-right]:before { content:'\e080'; } + +.oi[data-glyph=key]:before { content:'\e081'; } + +.oi[data-glyph=laptop]:before { content:'\e082'; } + +.oi[data-glyph=layers]:before { content:'\e083'; } + +.oi[data-glyph=lightbulb]:before { content:'\e084'; } + +.oi[data-glyph=link-broken]:before { content:'\e085'; } + +.oi[data-glyph=link-intact]:before { content:'\e086'; } + +.oi[data-glyph=list-rich]:before { content:'\e087'; } + +.oi[data-glyph=list]:before { content:'\e088'; } + +.oi[data-glyph=location]:before { content:'\e089'; } + +.oi[data-glyph=lock-locked]:before { content:'\e08a'; } + +.oi[data-glyph=lock-unlocked]:before { content:'\e08b'; } + +.oi[data-glyph=loop-circular]:before { content:'\e08c'; } + +.oi[data-glyph=loop-square]:before { content:'\e08d'; } + +.oi[data-glyph=loop]:before { content:'\e08e'; } + +.oi[data-glyph=magnifying-glass]:before { content:'\e08f'; } + +.oi[data-glyph=map-marker]:before { content:'\e090'; } + +.oi[data-glyph=map]:before { content:'\e091'; } + +.oi[data-glyph=media-pause]:before { content:'\e092'; } + +.oi[data-glyph=media-play]:before { content:'\e093'; } + +.oi[data-glyph=media-record]:before { content:'\e094'; } + +.oi[data-glyph=media-skip-backward]:before { content:'\e095'; } + +.oi[data-glyph=media-skip-forward]:before { content:'\e096'; } + +.oi[data-glyph=media-step-backward]:before { content:'\e097'; } + +.oi[data-glyph=media-step-forward]:before { content:'\e098'; } + +.oi[data-glyph=media-stop]:before { content:'\e099'; } + +.oi[data-glyph=medical-cross]:before { content:'\e09a'; } + +.oi[data-glyph=menu]:before { content:'\e09b'; } + +.oi[data-glyph=microphone]:before { content:'\e09c'; } + +.oi[data-glyph=minus]:before { content:'\e09d'; } + +.oi[data-glyph=monitor]:before { content:'\e09e'; } + +.oi[data-glyph=moon]:before { content:'\e09f'; } + +.oi[data-glyph=move]:before { content:'\e0a0'; } + +.oi[data-glyph=musical-note]:before { content:'\e0a1'; } + +.oi[data-glyph=paperclip]:before { content:'\e0a2'; } + +.oi[data-glyph=pencil]:before { content:'\e0a3'; } + +.oi[data-glyph=people]:before { content:'\e0a4'; } + +.oi[data-glyph=person]:before { content:'\e0a5'; } + +.oi[data-glyph=phone]:before { content:'\e0a6'; } + +.oi[data-glyph=pie-chart]:before { content:'\e0a7'; } + +.oi[data-glyph=pin]:before { content:'\e0a8'; } + +.oi[data-glyph=play-circle]:before { content:'\e0a9'; } + +.oi[data-glyph=plus]:before { content:'\e0aa'; } + +.oi[data-glyph=power-standby]:before { content:'\e0ab'; } + +.oi[data-glyph=print]:before { content:'\e0ac'; } + +.oi[data-glyph=project]:before { content:'\e0ad'; } + +.oi[data-glyph=pulse]:before { content:'\e0ae'; } + +.oi[data-glyph=puzzle-piece]:before { content:'\e0af'; } + +.oi[data-glyph=question-mark]:before { content:'\e0b0'; } + +.oi[data-glyph=rain]:before { content:'\e0b1'; } + +.oi[data-glyph=random]:before { content:'\e0b2'; } + +.oi[data-glyph=reload]:before { content:'\e0b3'; } + +.oi[data-glyph=resize-both]:before { content:'\e0b4'; } + +.oi[data-glyph=resize-height]:before { content:'\e0b5'; } + +.oi[data-glyph=resize-width]:before { content:'\e0b6'; } + +.oi[data-glyph=rss-alt]:before { content:'\e0b7'; } + +.oi[data-glyph=rss]:before { content:'\e0b8'; } + +.oi[data-glyph=script]:before { content:'\e0b9'; } + +.oi[data-glyph=share-boxed]:before { content:'\e0ba'; } + +.oi[data-glyph=share]:before { content:'\e0bb'; } + +.oi[data-glyph=shield]:before { content:'\e0bc'; } + +.oi[data-glyph=signal]:before { content:'\e0bd'; } + +.oi[data-glyph=signpost]:before { content:'\e0be'; } + +.oi[data-glyph=sort-ascending]:before { content:'\e0bf'; } + +.oi[data-glyph=sort-descending]:before { content:'\e0c0'; } + +.oi[data-glyph=spreadsheet]:before { content:'\e0c1'; } + +.oi[data-glyph=star]:before { content:'\e0c2'; } + +.oi[data-glyph=sun]:before { content:'\e0c3'; } + +.oi[data-glyph=tablet]:before { content:'\e0c4'; } + +.oi[data-glyph=tag]:before { content:'\e0c5'; } + +.oi[data-glyph=tags]:before { content:'\e0c6'; } + +.oi[data-glyph=target]:before { content:'\e0c7'; } + +.oi[data-glyph=task]:before { content:'\e0c8'; } + +.oi[data-glyph=terminal]:before { content:'\e0c9'; } + +.oi[data-glyph=text]:before { content:'\e0ca'; } + +.oi[data-glyph=thumb-down]:before { content:'\e0cb'; } + +.oi[data-glyph=thumb-up]:before { content:'\e0cc'; } + +.oi[data-glyph=timer]:before { content:'\e0cd'; } + +.oi[data-glyph=transfer]:before { content:'\e0ce'; } + +.oi[data-glyph=trash]:before { content:'\e0cf'; } + +.oi[data-glyph=underline]:before { content:'\e0d0'; } + +.oi[data-glyph=vertical-align-bottom]:before { content:'\e0d1'; } + +.oi[data-glyph=vertical-align-center]:before { content:'\e0d2'; } + +.oi[data-glyph=vertical-align-top]:before { content:'\e0d3'; } + +.oi[data-glyph=video]:before { content:'\e0d4'; } + +.oi[data-glyph=volume-high]:before { content:'\e0d5'; } + +.oi[data-glyph=volume-low]:before { content:'\e0d6'; } + +.oi[data-glyph=volume-off]:before { content:'\e0d7'; } + +.oi[data-glyph=warning]:before { content:'\e0d8'; } + +.oi[data-glyph=wifi]:before { content:'\e0d9'; } + +.oi[data-glyph=wrench]:before { content:'\e0da'; } + +.oi[data-glyph=x]:before { content:'\e0db'; } + +.oi[data-glyph=yen]:before { content:'\e0dc'; } + +.oi[data-glyph=zoom-in]:before { content:'\e0dd'; } + +.oi[data-glyph=zoom-out]:before { content:'\e0de'; } diff --git a/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.eot b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.eot new file mode 100644 index 000000000..7ca7c170f Binary files /dev/null and b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.eot differ diff --git a/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.otf b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.otf new file mode 100644 index 000000000..d79fb13a1 Binary files /dev/null and b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.otf differ diff --git a/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.svg b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.svg new file mode 100644 index 000000000..0792c003a --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.svg @@ -0,0 +1,543 @@ + + + + + +Created by FontForge 20120731 at Wed Apr 30 22:56:47 2014 + By P.J. Onori +Created by P.J. Onori with FontForge 2.0 (http://fontforge.sf.net) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.ttf b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.ttf new file mode 100644 index 000000000..0f94acd1e Binary files /dev/null and b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.ttf differ diff --git a/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.woff b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.woff new file mode 100644 index 000000000..793176af4 Binary files /dev/null and b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.woff differ diff --git a/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css b/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css new file mode 100644 index 000000000..c9ab2c74f --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css @@ -0,0 +1,228 @@ +/* Start of reset */ + +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +body { + margin: 0; +} + + +/** + * Define consistent border, margin, and padding. + */ +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} +td, +th { + padding: 0; +} + +/** End of reset */ + + +body { + font-family: 'Roboto', sans-serif; + font-size: 14px; + line-height: 22px; + font-weight: 300; +} +h1 { + font-size: 42px; + line-height: 44px; + padding-bottom: 5px; + color: #b10610; + margin-top: 10px; + margin-bottom: 30px; +} +h2 { + color: #333333; + font-size: 28px; + line-height: 44px; + font-weight: 300; +} +h3 { + font-size: 21px; + margin-top: 20px; + margin-bottom: 10px; +} +a { + color: #31a1cd; +} +h1 a { + text-decoration: none; +} +h2 a { + color: #333333; +} +a:visited { + color: #6098a2; +} +h2 a:visited { + color: #333333; +} +a:hover { + color: #b10610; +} +hr { + border: none; + border-top: 1px dashed #c9ea75; + margin-top: 30px; + margin-bottom: 30px; +} +header { + background: #eeeeee; +} +header a { + font-size: 28px; + font-weight: 500; + color: #333; + text-decoration: none; +} +.logo { + padding: 5px 10px; +} +.logo img { + vertical-align: middle; + border: 0; +} +input, button { + font: inherit; + color: inherit; +} + +input[type=text] { + border: 1px solid #bbbbbb; + line-height: 22px; + padding: 5px 10px; + border-radius: 3px; +} + +nav { + padding: 5px; +} + +.btn, button, input[type=submit] { + display: inline-block; + color: white; + background: #4fa3ac; + padding: 9px 15px; + border-radius: 2px; + border: 0; + text-decoration: none; +} +a.btn:visited { + color: white; +} + +.btn.disabled { + background: #eeeeee; + color: #bbbbbb; +} +section { + margin: 40px 10px; +} + +section table { + height: 40px; +} + +.nodeTable tr { + border-bottom: 3px solid white; +} + +.nodeTable td { + padding: 10px 10px 10px 10px; + +} + +.nodeTable a { + text-decoration: none; +} + +.nodeTable .nameColumn { + font-weight: bold; + padding: 10px 20px; + background: #ebf5f6; + min-width: 200px; +} +.nodeTable .oi { + color: #b10610; +} + +.propTable tr { + height: 40px; +} + +.propTable th { + background: #f6f6f6; + padding: 0 10px; + text-align: left; +} + +.propTable td { + padding: 0 10px; + background: #eeeeee; +} + +.propTable pre { + font-size: 80%; + background: #f8f8f8; +} + +.actions { + border: 1px dotted #76baa6; + padding: 20px; + margin-bottom: 20px; + +} + +.actions h3 { + margin-top: 10px; + margin-bottom: 30px; + padding-bottom: 20px; + border-bottom: 1px solid #eeeeee; +} + +.actions label { + width: 150px; + display: inline-block; + line-height: 40px; +} + +.actions input[type=text] { + width: 450px; +} + +.actions input[type=submit] { + display: inline-block; + margin-left: 153px; +} + +footer { + padding: 50px 0; + font-size: 80%; + text-align: center; +} + +ul.tree { + list-style: none; + margin: 0; + padding: 0; +} + +ul.tree ul { + list-style: none; + padding-left: 10px; + border-left: 4px solid #ccc; +} diff --git a/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.png b/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.png new file mode 100644 index 000000000..48a97398a Binary files /dev/null and b/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.png differ diff --git a/vendor/sabre/dav/lib/DAV/Client.php b/vendor/sabre/dav/lib/DAV/Client.php new file mode 100644 index 000000000..d46b397b6 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Client.php @@ -0,0 +1,448 @@ +xml->elementMap. + * It's deprecated as of version 3.0.0, and should no longer be used. + * + * @deprecated + * @var array + */ + public $propertyMap = []; + + /** + * Base URI + * + * This URI will be used to resolve relative urls. + * + * @var string + */ + protected $baseUri; + + /** + * Basic authentication + */ + const AUTH_BASIC = 1; + + /** + * Digest authentication + */ + const AUTH_DIGEST = 2; + + /** + * NTLM authentication + */ + const AUTH_NTLM = 4; + + /** + * Identity encoding, which basically does not nothing. + */ + const ENCODING_IDENTITY = 1; + + /** + * Deflate encoding + */ + const ENCODING_DEFLATE = 2; + + /** + * Gzip encoding + */ + const ENCODING_GZIP = 4; + + /** + * Sends all encoding headers. + */ + const ENCODING_ALL = 7; + + /** + * Content-encoding + * + * @var int + */ + protected $encoding = self::ENCODING_IDENTITY; + + /** + * Constructor + * + * Settings are provided through the 'settings' argument. The following + * settings are supported: + * + * * baseUri + * * userName (optional) + * * password (optional) + * * proxy (optional) + * * authType (optional) + * * encoding (optional) + * + * authType must be a bitmap, using self::AUTH_BASIC, self::AUTH_DIGEST + * and self::AUTH_NTLM. If you know which authentication method will be + * used, it's recommended to set it, as it will save a great deal of + * requests to 'discover' this information. + * + * Encoding is a bitmap with one of the ENCODING constants. + * + * @param array $settings + */ + function __construct(array $settings) { + + if (!isset($settings['baseUri'])) { + throw new \InvalidArgumentException('A baseUri must be provided'); + } + + parent::__construct(); + + $this->baseUri = $settings['baseUri']; + + if (isset($settings['proxy'])) { + $this->addCurlSetting(CURLOPT_PROXY, $settings['proxy']); + } + + if (isset($settings['userName'])) { + $userName = $settings['userName']; + $password = isset($settings['password']) ? $settings['password'] : ''; + + if (isset($settings['authType'])) { + $curlType = 0; + if ($settings['authType'] & self::AUTH_BASIC) { + $curlType |= CURLAUTH_BASIC; + } + if ($settings['authType'] & self::AUTH_DIGEST) { + $curlType |= CURLAUTH_DIGEST; + } + if ($settings['authType'] & self::AUTH_NTLM) { + $curlType |= CURLAUTH_NTLM; + } + } else { + $curlType = CURLAUTH_BASIC | CURLAUTH_DIGEST; + } + + $this->addCurlSetting(CURLOPT_HTTPAUTH, $curlType); + $this->addCurlSetting(CURLOPT_USERPWD, $userName . ':' . $password); + + } + + if (isset($settings['encoding'])) { + $encoding = $settings['encoding']; + + $encodings = []; + if ($encoding & self::ENCODING_IDENTITY) { + $encodings[] = 'identity'; + } + if ($encoding & self::ENCODING_DEFLATE) { + $encodings[] = 'deflate'; + } + if ($encoding & self::ENCODING_GZIP) { + $encodings[] = 'gzip'; + } + $this->addCurlSetting(CURLOPT_ENCODING, implode(',', $encodings)); + } + + $this->addCurlSetting(CURLOPT_USERAGENT, 'sabre-dav/' . Version::VERSION . ' (http://sabre.io/)'); + + $this->xml = new Xml\Service(); + // BC + $this->propertyMap = & $this->xml->elementMap; + + } + + /** + * Does a PROPFIND request + * + * The list of requested properties must be specified as an array, in clark + * notation. + * + * The returned array will contain a list of filenames as keys, and + * properties as values. + * + * The properties array will contain the list of properties. Only properties + * that are actually returned from the server (without error) will be + * returned, anything else is discarded. + * + * Depth should be either 0 or 1. A depth of 1 will cause a request to be + * made to the server to also return all child resources. + * + * @param string $url + * @param array $properties + * @param int $depth + * @return array + */ + function propFind($url, array $properties, $depth = 0) { + + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->formatOutput = true; + $root = $dom->createElementNS('DAV:', 'd:propfind'); + $prop = $dom->createElement('d:prop'); + + foreach ($properties as $property) { + + list( + $namespace, + $elementName + ) = \Sabre\Xml\Service::parseClarkNotation($property); + + if ($namespace === 'DAV:') { + $element = $dom->createElement('d:' . $elementName); + } else { + $element = $dom->createElementNS($namespace, 'x:' . $elementName); + } + + $prop->appendChild($element); + } + + $dom->appendChild($root)->appendChild($prop); + $body = $dom->saveXML(); + + $url = $this->getAbsoluteUrl($url); + + $request = new HTTP\Request('PROPFIND', $url, [ + 'Depth' => $depth, + 'Content-Type' => 'application/xml' + ], $body); + + $response = $this->send($request); + + if ((int)$response->getStatus() >= 400) { + throw new \Sabre\HTTP\ClientHttpException($response); + } + + $result = $this->parseMultiStatus($response->getBodyAsString()); + + // If depth was 0, we only return the top item + if ($depth === 0) { + reset($result); + $result = current($result); + return isset($result[200]) ? $result[200] : []; + } + + $newResult = []; + foreach ($result as $href => $statusList) { + + $newResult[$href] = isset($statusList[200]) ? $statusList[200] : []; + + } + + return $newResult; + + } + + /** + * Updates a list of properties on the server + * + * The list of properties must have clark-notation properties for the keys, + * and the actual (string) value for the value. If the value is null, an + * attempt is made to delete the property. + * + * @param string $url + * @param array $properties + * @return bool + */ + function propPatch($url, array $properties) { + + $propPatch = new Xml\Request\PropPatch(); + $propPatch->properties = $properties; + $xml = $this->xml->write( + '{DAV:}propertyupdate', + $propPatch + ); + + $url = $this->getAbsoluteUrl($url); + $request = new HTTP\Request('PROPPATCH', $url, [ + 'Content-Type' => 'application/xml', + ], $xml); + $response = $this->send($request); + + if ($response->getStatus() >= 400) { + throw new \Sabre\HTTP\ClientHttpException($response); + } + + if ($response->getStatus() === 207) { + // If it's a 207, the request could still have failed, but the + // information is hidden in the response body. + $result = $this->parseMultiStatus($response->getBodyAsString()); + + $errorProperties = []; + foreach ($result as $href => $statusList) { + foreach ($statusList as $status => $properties) { + + if ($status >= 400) { + foreach ($properties as $propName => $propValue) { + $errorProperties[] = $propName . ' (' . $status . ')'; + } + } + + } + } + if ($errorProperties) { + + throw new \Sabre\HTTP\ClientException('PROPPATCH failed. The following properties errored: ' . implode(', ', $errorProperties)); + } + } + return true; + + } + + /** + * Performs an HTTP options request + * + * This method returns all the features from the 'DAV:' header as an array. + * If there was no DAV header, or no contents this method will return an + * empty array. + * + * @return array + */ + function options() { + + $request = new HTTP\Request('OPTIONS', $this->getAbsoluteUrl('')); + $response = $this->send($request); + + $dav = $response->getHeader('Dav'); + if (!$dav) { + return []; + } + + $features = explode(',', $dav); + foreach ($features as &$v) { + $v = trim($v); + } + return $features; + + } + + /** + * Performs an actual HTTP request, and returns the result. + * + * If the specified url is relative, it will be expanded based on the base + * url. + * + * The returned array contains 3 keys: + * * body - the response body + * * httpCode - a HTTP code (200, 404, etc) + * * headers - a list of response http headers. The header names have + * been lowercased. + * + * For large uploads, it's highly recommended to specify body as a stream + * resource. You can easily do this by simply passing the result of + * fopen(..., 'r'). + * + * This method will throw an exception if an HTTP error was received. Any + * HTTP status code above 399 is considered an error. + * + * Note that it is no longer recommended to use this method, use the send() + * method instead. + * + * @param string $method + * @param string $url + * @param string|resource|null $body + * @param array $headers + * @throws ClientException, in case a curl error occurred. + * @return array + */ + function request($method, $url = '', $body = null, array $headers = []) { + + $url = $this->getAbsoluteUrl($url); + + $response = $this->send(new HTTP\Request($method, $url, $headers, $body)); + return [ + 'body' => $response->getBodyAsString(), + 'statusCode' => (int)$response->getStatus(), + 'headers' => array_change_key_case($response->getHeaders()), + ]; + + } + + /** + * Returns the full url based on the given url (which may be relative). All + * urls are expanded based on the base url as given by the server. + * + * @param string $url + * @return string + */ + function getAbsoluteUrl($url) { + + // If the url starts with http:// or https://, the url is already absolute. + if (preg_match('/^http(s?):\/\//', $url)) { + return $url; + } + + // If the url starts with a slash, we must calculate the url based off + // the root of the base url. + if (strpos($url, '/') === 0) { + $parts = parse_url($this->baseUri); + return $parts['scheme'] . '://' . $parts['host'] . (isset($parts['port']) ? ':' . $parts['port'] : '') . $url; + } + + // Otherwise... + return $this->baseUri . $url; + + } + + /** + * Parses a WebDAV multistatus response body + * + * This method returns an array with the following structure + * + * [ + * 'url/to/resource' => [ + * '200' => [ + * '{DAV:}property1' => 'value1', + * '{DAV:}property2' => 'value2', + * ], + * '404' => [ + * '{DAV:}property1' => null, + * '{DAV:}property2' => null, + * ], + * ], + * 'url/to/resource2' => [ + * .. etc .. + * ] + * ] + * + * + * @param string $body xml body + * @return array + */ + function parseMultiStatus($body) { + + $multistatus = $this->xml->expect('{DAV:}multistatus', $body); + + $result = []; + + foreach ($multistatus->getResponses() as $response) { + + $result[$response->getHref()] = $response->getResponseProperties(); + + } + + return $result; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Collection.php b/vendor/sabre/dav/lib/DAV/Collection.php new file mode 100644 index 000000000..a46bcc342 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Collection.php @@ -0,0 +1,109 @@ +getChildren() as $child) { + + if ($child->getName() === $name) return $child; + + } + throw new Exception\NotFound('File not found: ' . $name); + + } + + /** + * Checks is a child-node exists. + * + * It is generally a good idea to try and override this. Usually it can be optimized. + * + * @param string $name + * @return bool + */ + function childExists($name) { + + try { + + $this->getChild($name); + return true; + + } catch (Exception\NotFound $e) { + + return false; + + } + + } + + /** + * Creates a new file in the directory + * + * Data will either be supplied as a stream resource, or in certain cases + * as a string. Keep in mind that you may have to support either. + * + * After succesful creation of the file, you may choose to return the ETag + * of the new file here. + * + * The returned ETag must be surrounded by double-quotes (The quotes should + * be part of the actual string). + * + * If you cannot accurately determine the ETag, you should not return it. + * If you don't store the file exactly as-is (you're transforming it + * somehow) you should also not return an ETag. + * + * This means that if a subsequent GET to this new file does not exactly + * return the same contents of what was submitted here, you are strongly + * recommended to omit the ETag. + * + * @param string $name Name of the file + * @param resource|string $data Initial payload + * @return null|string + */ + function createFile($name, $data = null) { + + throw new Exception\Forbidden('Permission denied to create file (filename ' . $name . ')'); + + } + + /** + * Creates a new subdirectory + * + * @param string $name + * @throws Exception\Forbidden + * @return void + */ + function createDirectory($name) { + + throw new Exception\Forbidden('Permission denied to create directory'); + + } + + +} diff --git a/vendor/sabre/dav/lib/DAV/CorePlugin.php b/vendor/sabre/dav/lib/DAV/CorePlugin.php new file mode 100644 index 000000000..3a70b2a7e --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/CorePlugin.php @@ -0,0 +1,927 @@ +server = $server; + $server->on('method:GET', [$this, 'httpGet']); + $server->on('method:OPTIONS', [$this, 'httpOptions']); + $server->on('method:HEAD', [$this, 'httpHead']); + $server->on('method:DELETE', [$this, 'httpDelete']); + $server->on('method:PROPFIND', [$this, 'httpPropFind']); + $server->on('method:PROPPATCH', [$this, 'httpPropPatch']); + $server->on('method:PUT', [$this, 'httpPut']); + $server->on('method:MKCOL', [$this, 'httpMkcol']); + $server->on('method:MOVE', [$this, 'httpMove']); + $server->on('method:COPY', [$this, 'httpCopy']); + $server->on('method:REPORT', [$this, 'httpReport']); + + $server->on('propPatch', [$this, 'propPatchProtectedPropertyCheck'], 90); + $server->on('propPatch', [$this, 'propPatchNodeUpdate'], 200); + $server->on('propFind', [$this, 'propFind']); + $server->on('propFind', [$this, 'propFindNode'], 120); + $server->on('propFind', [$this, 'propFindLate'], 200); + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'core'; + + } + + /** + * This is the default implementation for the GET method. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpGet(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + $node = $this->server->tree->getNodeForPath($path); + + if (!$node instanceof IFile) return; + + $body = $node->get(); + + // Converting string into stream, if needed. + if (is_string($body)) { + $stream = fopen('php://temp', 'r+'); + fwrite($stream, $body); + rewind($stream); + $body = $stream; + } + + /* + * TODO: getetag, getlastmodified, getsize should also be used using + * this method + */ + $httpHeaders = $this->server->getHTTPHeaders($path); + + /* ContentType needs to get a default, because many webservers will otherwise + * default to text/html, and we don't want this for security reasons. + */ + if (!isset($httpHeaders['Content-Type'])) { + $httpHeaders['Content-Type'] = 'application/octet-stream'; + } + + + if (isset($httpHeaders['Content-Length'])) { + + $nodeSize = $httpHeaders['Content-Length']; + + // Need to unset Content-Length, because we'll handle that during figuring out the range + unset($httpHeaders['Content-Length']); + + } else { + $nodeSize = null; + } + + $response->addHeaders($httpHeaders); + + $range = $this->server->getHTTPRange(); + $ifRange = $request->getHeader('If-Range'); + $ignoreRangeHeader = false; + + // If ifRange is set, and range is specified, we first need to check + // the precondition. + if ($nodeSize && $range && $ifRange) { + + // if IfRange is parsable as a date we'll treat it as a DateTime + // otherwise, we must treat it as an etag. + try { + $ifRangeDate = new \DateTime($ifRange); + + // It's a date. We must check if the entity is modified since + // the specified date. + if (!isset($httpHeaders['Last-Modified'])) $ignoreRangeHeader = true; + else { + $modified = new \DateTime($httpHeaders['Last-Modified']); + if ($modified > $ifRangeDate) $ignoreRangeHeader = true; + } + + } catch (\Exception $e) { + + // It's an entity. We can do a simple comparison. + if (!isset($httpHeaders['ETag'])) $ignoreRangeHeader = true; + elseif ($httpHeaders['ETag'] !== $ifRange) $ignoreRangeHeader = true; + } + } + + // We're only going to support HTTP ranges if the backend provided a filesize + if (!$ignoreRangeHeader && $nodeSize && $range) { + + // Determining the exact byte offsets + if (!is_null($range[0])) { + + $start = $range[0]; + $end = $range[1] ? $range[1] : $nodeSize - 1; + if ($start >= $nodeSize) + throw new Exception\RequestedRangeNotSatisfiable('The start offset (' . $range[0] . ') exceeded the size of the entity (' . $nodeSize . ')'); + + if ($end < $start) throw new Exception\RequestedRangeNotSatisfiable('The end offset (' . $range[1] . ') is lower than the start offset (' . $range[0] . ')'); + if ($end >= $nodeSize) $end = $nodeSize - 1; + + } else { + + $start = $nodeSize - $range[1]; + $end = $nodeSize - 1; + + if ($start < 0) $start = 0; + + } + + // Streams may advertise themselves as seekable, but still not + // actually allow fseek. We'll manually go forward in the stream + // if fseek failed. + if (!stream_get_meta_data($body)['seekable'] || fseek($body, $start, SEEK_SET) === -1) { + $consumeBlock = 8192; + for ($consumed = 0; $start - $consumed > 0;){ + if (feof($body)) throw new Exception\RequestedRangeNotSatisfiable('The start offset (' . $start . ') exceeded the size of the entity (' . $consumed . ')'); + $consumed += strlen(fread($body, min($start - $consumed, $consumeBlock))); + } + } + + $response->setHeader('Content-Length', $end - $start + 1); + $response->setHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $nodeSize); + $response->setStatus(206); + $response->setBody($body); + + } else { + + if ($nodeSize) $response->setHeader('Content-Length', $nodeSize); + $response->setStatus(200); + $response->setBody($body); + + } + // Sending back false will interupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * HTTP OPTIONS + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpOptions(RequestInterface $request, ResponseInterface $response) { + + $methods = $this->server->getAllowedMethods($request->getPath()); + + $response->setHeader('Allow', strtoupper(implode(', ', $methods))); + $features = ['1', '3', 'extended-mkcol']; + + foreach ($this->server->getPlugins() as $plugin) { + $features = array_merge($features, $plugin->getFeatures()); + } + + $response->setHeader('DAV', implode(', ', $features)); + $response->setHeader('MS-Author-Via', 'DAV'); + $response->setHeader('Accept-Ranges', 'bytes'); + $response->setHeader('Content-Length', '0'); + $response->setStatus(200); + + // Sending back false will interupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * HTTP HEAD + * + * This method is normally used to take a peak at a url, and only get the + * HTTP response headers, without the body. This is used by clients to + * determine if a remote file was changed, so they can use a local cached + * version, instead of downloading it again + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpHead(RequestInterface $request, ResponseInterface $response) { + + // This is implemented by changing the HEAD request to a GET request, + // and dropping the response body. + $subRequest = clone $request; + $subRequest->setMethod('GET'); + + try { + $this->server->invokeMethod($subRequest, $response, false); + $response->setBody(''); + } catch (Exception\NotImplemented $e) { + // Some clients may do HEAD requests on collections, however, GET + // requests and HEAD requests _may_ not be defined on a collection, + // which would trigger a 501. + // This breaks some clients though, so we're transforming these + // 501s into 200s. + $response->setStatus(200); + $response->setBody(''); + $response->setHeader('Content-Type', 'text/plain'); + $response->setHeader('X-Sabre-Real-Status', $e->getHTTPCode()); + } + + // Sending back false will interupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * HTTP Delete + * + * The HTTP delete method, deletes a given uri + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function httpDelete(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + if (!$this->server->emit('beforeUnbind', [$path])) return false; + $this->server->tree->delete($path); + $this->server->emit('afterUnbind', [$path]); + + $response->setStatus(204); + $response->setHeader('Content-Length', '0'); + + // Sending back false will interupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * WebDAV PROPFIND + * + * This WebDAV method requests information about an uri resource, or a list of resources + * If a client wants to receive the properties for a single resource it will add an HTTP Depth: header with a 0 value + * If the value is 1, it means that it also expects a list of sub-resources (e.g.: files in a directory) + * + * The request body contains an XML data structure that has a list of properties the client understands + * The response body is also an xml document, containing information about every uri resource and the requested properties + * + * It has to return a HTTP 207 Multi-status status code + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function httpPropFind(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + $requestBody = $request->getBodyAsString(); + if (strlen($requestBody)) { + try { + $propFindXml = $this->server->xml->expect('{DAV:}propfind', $requestBody); + } catch (ParseException $e) { + throw new BadRequest($e->getMessage(), null, $e); + } + } else { + $propFindXml = new Xml\Request\PropFind(); + $propFindXml->allProp = true; + $propFindXml->properties = []; + } + + $depth = $this->server->getHTTPDepth(1); + // The only two options for the depth of a propfind is 0 or 1 - as long as depth infinity is not enabled + if (!$this->server->enablePropfindDepthInfinity && $depth != 0) $depth = 1; + + $newProperties = $this->server->getPropertiesForPath($path, $propFindXml->properties, $depth); + + // This is a multi-status response + $response->setStatus(207); + $response->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $response->setHeader('Vary', 'Brief,Prefer'); + + // Normally this header is only needed for OPTIONS responses, however.. + // iCal seems to also depend on these being set for PROPFIND. Since + // this is not harmful, we'll add it. + $features = ['1', '3', 'extended-mkcol']; + foreach ($this->server->getPlugins() as $plugin) { + $features = array_merge($features, $plugin->getFeatures()); + } + $response->setHeader('DAV', implode(', ', $features)); + + $prefer = $this->server->getHTTPPrefer(); + $minimal = $prefer['return'] === 'minimal'; + + $data = $this->server->generateMultiStatus($newProperties, $minimal); + $response->setBody($data); + + // Sending back false will interupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * WebDAV PROPPATCH + * + * This method is called to update properties on a Node. The request is an XML body with all the mutations. + * In this XML body it is specified which properties should be set/updated and/or deleted + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpPropPatch(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + try { + $propPatch = $this->server->xml->expect('{DAV:}propertyupdate', $request->getBody()); + } catch (ParseException $e) { + throw new BadRequest($e->getMessage(), null, $e); + } + $newProperties = $propPatch->properties; + + $result = $this->server->updateProperties($path, $newProperties); + + $prefer = $this->server->getHTTPPrefer(); + $response->setHeader('Vary', 'Brief,Prefer'); + + if ($prefer['return'] === 'minimal') { + + // If return-minimal is specified, we only have to check if the + // request was succesful, and don't need to return the + // multi-status. + $ok = true; + foreach ($result as $prop => $code) { + if ((int)$code > 299) { + $ok = false; + } + } + + if ($ok) { + + $response->setStatus(204); + return false; + + } + + } + + $response->setStatus(207); + $response->setHeader('Content-Type', 'application/xml; charset=utf-8'); + + + // Reorganizing the result for generateMultiStatus + $multiStatus = []; + foreach ($result as $propertyName => $code) { + if (isset($multiStatus[$code])) { + $multiStatus[$code][$propertyName] = null; + } else { + $multiStatus[$code] = [$propertyName => null]; + } + } + $multiStatus['href'] = $path; + + $response->setBody( + $this->server->generateMultiStatus([$multiStatus]) + ); + + // Sending back false will interupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * HTTP PUT method + * + * This HTTP method updates a file, or creates a new one. + * + * If a new resource was created, a 201 Created status code should be returned. If an existing resource is updated, it's a 204 No Content + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpPut(RequestInterface $request, ResponseInterface $response) { + + $body = $request->getBodyAsStream(); + $path = $request->getPath(); + + // Intercepting Content-Range + if ($request->getHeader('Content-Range')) { + /* + An origin server that allows PUT on a given target resource MUST send + a 400 (Bad Request) response to a PUT request that contains a + Content-Range header field. + + Reference: http://tools.ietf.org/html/rfc7231#section-4.3.4 + */ + throw new Exception\BadRequest('Content-Range on PUT requests are forbidden.'); + } + + // Intercepting the Finder problem + if (($expected = $request->getHeader('X-Expected-Entity-Length')) && $expected > 0) { + + /* + Many webservers will not cooperate well with Finder PUT requests, + because it uses 'Chunked' transfer encoding for the request body. + + The symptom of this problem is that Finder sends files to the + server, but they arrive as 0-length files in PHP. + + If we don't do anything, the user might think they are uploading + files successfully, but they end up empty on the server. Instead, + we throw back an error if we detect this. + + The reason Finder uses Chunked, is because it thinks the files + might change as it's being uploaded, and therefore the + Content-Length can vary. + + Instead it sends the X-Expected-Entity-Length header with the size + of the file at the very start of the request. If this header is set, + but we don't get a request body we will fail the request to + protect the end-user. + */ + + // Only reading first byte + $firstByte = fread($body, 1); + if (strlen($firstByte) !== 1) { + throw new Exception\Forbidden('This server is not compatible with OS/X finder. Consider using a different WebDAV client or webserver.'); + } + + // The body needs to stay intact, so we copy everything to a + // temporary stream. + + $newBody = fopen('php://temp', 'r+'); + fwrite($newBody, $firstByte); + stream_copy_to_stream($body, $newBody); + rewind($newBody); + + $body = $newBody; + + } + + if ($this->server->tree->nodeExists($path)) { + + $node = $this->server->tree->getNodeForPath($path); + + // If the node is a collection, we'll deny it + if (!($node instanceof IFile)) throw new Exception\Conflict('PUT is not allowed on non-files.'); + + if (!$this->server->updateFile($path, $body, $etag)) { + return false; + } + + $response->setHeader('Content-Length', '0'); + if ($etag) $response->setHeader('ETag', $etag); + $response->setStatus(204); + + } else { + + $etag = null; + // If we got here, the resource didn't exist yet. + if (!$this->server->createFile($path, $body, $etag)) { + // For one reason or another the file was not created. + return false; + } + + $response->setHeader('Content-Length', '0'); + if ($etag) $response->setHeader('ETag', $etag); + $response->setStatus(201); + + } + + // Sending back false will interupt the event chain and tell the server + // we've handled this method. + return false; + + } + + + /** + * WebDAV MKCOL + * + * The MKCOL method is used to create a new collection (directory) on the server + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpMkcol(RequestInterface $request, ResponseInterface $response) { + + $requestBody = $request->getBodyAsString(); + $path = $request->getPath(); + + if ($requestBody) { + + $contentType = $request->getHeader('Content-Type'); + if (strpos($contentType, 'application/xml') !== 0 && strpos($contentType, 'text/xml') !== 0) { + + // We must throw 415 for unsupported mkcol bodies + throw new Exception\UnsupportedMediaType('The request body for the MKCOL request must have an xml Content-Type'); + + } + + try { + $mkcol = $this->server->xml->expect('{DAV:}mkcol', $requestBody); + } catch (\Sabre\Xml\ParseException $e) { + throw new Exception\BadRequest($e->getMessage(), null, $e); + } + + $properties = $mkcol->getProperties(); + + if (!isset($properties['{DAV:}resourcetype'])) + throw new Exception\BadRequest('The mkcol request must include a {DAV:}resourcetype property'); + + $resourceType = $properties['{DAV:}resourcetype']->getValue(); + unset($properties['{DAV:}resourcetype']); + + } else { + + $properties = []; + $resourceType = ['{DAV:}collection']; + + } + + $mkcol = new MkCol($resourceType, $properties); + + $result = $this->server->createCollection($path, $mkcol); + + if (is_array($result)) { + $response->setStatus(207); + $response->setHeader('Content-Type', 'application/xml; charset=utf-8'); + + $response->setBody( + $this->server->generateMultiStatus([$result]) + ); + + } else { + $response->setHeader('Content-Length', '0'); + $response->setStatus(201); + } + + // Sending back false will interupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * WebDAV HTTP MOVE method + * + * This method moves one uri to a different uri. A lot of the actual request processing is done in getCopyMoveInfo + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpMove(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + $moveInfo = $this->server->getCopyAndMoveInfo($request); + + if ($moveInfo['destinationExists']) { + + if (!$this->server->emit('beforeUnbind', [$moveInfo['destination']])) return false; + + } + if (!$this->server->emit('beforeUnbind', [$path])) return false; + if (!$this->server->emit('beforeBind', [$moveInfo['destination']])) return false; + if (!$this->server->emit('beforeMove', [$path, $moveInfo['destination']])) return false; + + if ($moveInfo['destinationExists']) { + + $this->server->tree->delete($moveInfo['destination']); + $this->server->emit('afterUnbind', [$moveInfo['destination']]); + + } + + $this->server->tree->move($path, $moveInfo['destination']); + + // Its important afterMove is called before afterUnbind, because it + // allows systems to transfer data from one path to another. + // PropertyStorage uses this. If afterUnbind was first, it would clean + // up all the properties before it has a chance. + $this->server->emit('afterMove', [$path, $moveInfo['destination']]); + $this->server->emit('afterUnbind', [$path]); + $this->server->emit('afterBind', [$moveInfo['destination']]); + + // If a resource was overwritten we should send a 204, otherwise a 201 + $response->setHeader('Content-Length', '0'); + $response->setStatus($moveInfo['destinationExists'] ? 204 : 201); + + // Sending back false will interupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * WebDAV HTTP COPY method + * + * This method copies one uri to a different uri, and works much like the MOVE request + * A lot of the actual request processing is done in getCopyMoveInfo + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpCopy(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + $copyInfo = $this->server->getCopyAndMoveInfo($request); + + if (!$this->server->emit('beforeBind', [$copyInfo['destination']])) return false; + if ($copyInfo['destinationExists']) { + if (!$this->server->emit('beforeUnbind', [$copyInfo['destination']])) return false; + $this->server->tree->delete($copyInfo['destination']); + } + + $this->server->tree->copy($path, $copyInfo['destination']); + $this->server->emit('afterBind', [$copyInfo['destination']]); + + // If a resource was overwritten we should send a 204, otherwise a 201 + $response->setHeader('Content-Length', '0'); + $response->setStatus($copyInfo['destinationExists'] ? 204 : 201); + + // Sending back false will interupt the event chain and tell the server + // we've handled this method. + return false; + + + } + + /** + * HTTP REPORT method implementation + * + * Although the REPORT method is not part of the standard WebDAV spec (it's from rfc3253) + * It's used in a lot of extensions, so it made sense to implement it into the core. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpReport(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + $result = $this->server->xml->parse( + $request->getBody(), + $request->getUrl(), + $rootElementName + ); + + if ($this->server->emit('report', [$rootElementName, $result, $path])) { + + // If emit returned true, it means the report was not supported + throw new Exception\ReportNotSupported(); + + } + + // Sending back false will interupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * This method is called during property updates. + * + * Here we check if a user attempted to update a protected property and + * ensure that the process fails if this is the case. + * + * @param string $path + * @param PropPatch $propPatch + * @return void + */ + function propPatchProtectedPropertyCheck($path, PropPatch $propPatch) { + + // Comparing the mutation list to the list of propetected properties. + $mutations = $propPatch->getMutations(); + + $protected = array_intersect( + $this->server->protectedProperties, + array_keys($mutations) + ); + + if ($protected) { + $propPatch->setResultCode($protected, 403); + } + + } + + /** + * This method is called during property updates. + * + * Here we check if a node implements IProperties and let the node handle + * updating of (some) properties. + * + * @param string $path + * @param PropPatch $propPatch + * @return void + */ + function propPatchNodeUpdate($path, PropPatch $propPatch) { + + // This should trigger a 404 if the node doesn't exist. + $node = $this->server->tree->getNodeForPath($path); + + if ($node instanceof IProperties) { + $node->propPatch($propPatch); + } + + } + + /** + * This method is called when properties are retrieved. + * + * Here we add all the default properties. + * + * @param PropFind $propFind + * @param INode $node + * @return void + */ + function propFind(PropFind $propFind, INode $node) { + + $propFind->handle('{DAV:}getlastmodified', function() use ($node) { + $lm = $node->getLastModified(); + if ($lm) { + return new Xml\Property\GetLastModified($lm); + } + }); + + if ($node instanceof IFile) { + $propFind->handle('{DAV:}getcontentlength', [$node, 'getSize']); + $propFind->handle('{DAV:}getetag', [$node, 'getETag']); + $propFind->handle('{DAV:}getcontenttype', [$node, 'getContentType']); + } + + if ($node instanceof IQuota) { + $quotaInfo = null; + $propFind->handle('{DAV:}quota-used-bytes', function() use (&$quotaInfo, $node) { + $quotaInfo = $node->getQuotaInfo(); + return $quotaInfo[0]; + }); + $propFind->handle('{DAV:}quota-available-bytes', function() use (&$quotaInfo, $node) { + if (!$quotaInfo) { + $quotaInfo = $node->getQuotaInfo(); + } + return $quotaInfo[1]; + }); + } + + $propFind->handle('{DAV:}supported-report-set', function() use ($propFind) { + $reports = []; + foreach ($this->server->getPlugins() as $plugin) { + $reports = array_merge($reports, $plugin->getSupportedReportSet($propFind->getPath())); + } + return new Xml\Property\SupportedReportSet($reports); + }); + $propFind->handle('{DAV:}resourcetype', function() use ($node) { + return new Xml\Property\ResourceType($this->server->getResourceTypeForNode($node)); + }); + $propFind->handle('{DAV:}supported-method-set', function() use ($propFind) { + return new Xml\Property\SupportedMethodSet( + $this->server->getAllowedMethods($propFind->getPath()) + ); + }); + + } + + /** + * Fetches properties for a node. + * + * This event is called a bit later, so plugins have a chance first to + * populate the result. + * + * @param PropFind $propFind + * @param INode $node + * @return void + */ + function propFindNode(PropFind $propFind, INode $node) { + + if ($node instanceof IProperties && $propertyNames = $propFind->get404Properties()) { + + $nodeProperties = $node->getProperties($propertyNames); + foreach ($propertyNames as $propertyName) { + if (array_key_exists($propertyName, $nodeProperties)) { + $propFind->set($propertyName, $nodeProperties[$propertyName], 200); + } + } + + } + + } + + /** + * This method is called when properties are retrieved. + * + * This specific handler is called very late in the process, because we + * want other systems to first have a chance to handle the properties. + * + * @param PropFind $propFind + * @param INode $node + * @return void + */ + function propFindLate(PropFind $propFind, INode $node) { + + $propFind->handle('{http://calendarserver.org/ns/}getctag', function() use ($propFind) { + + // If we already have a sync-token from the current propFind + // request, we can re-use that. + $val = $propFind->get('{http://sabredav.org/ns}sync-token'); + if ($val) return $val; + + $val = $propFind->get('{DAV:}sync-token'); + if ($val && is_scalar($val)) { + return $val; + } + if ($val && $val instanceof Xml\Property\Href) { + return substr($val->getHref(), strlen(Sync\Plugin::SYNCTOKEN_PREFIX)); + } + + // If we got here, the earlier two properties may simply not have + // been part of the earlier request. We're going to fetch them. + $result = $this->server->getProperties($propFind->getPath(), [ + '{http://sabredav.org/ns}sync-token', + '{DAV:}sync-token', + ]); + + if (isset($result['{http://sabredav.org/ns}sync-token'])) { + return $result['{http://sabredav.org/ns}sync-token']; + } + if (isset($result['{DAV:}sync-token'])) { + $val = $result['{DAV:}sync-token']; + if (is_scalar($val)) { + return $val; + } elseif ($val instanceof Xml\Property\Href) { + return substr($val->getHref(), strlen(Sync\Plugin::SYNCTOKEN_PREFIX)); + } + } + + }); + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'The Core plugin provides a lot of the basic functionality required by WebDAV, such as a default implementation for all HTTP and WebDAV methods.', + 'link' => null, + ]; + + } +} diff --git a/vendor/sabre/dav/lib/DAV/Exception.php b/vendor/sabre/dav/lib/DAV/Exception.php new file mode 100644 index 000000000..14f5bab2a --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception.php @@ -0,0 +1,57 @@ +lock) { + $error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:no-conflicting-lock'); + $errorNode->appendChild($error); + $error->appendChild($errorNode->ownerDocument->createElementNS('DAV:', 'd:href', $this->lock->uri)); + } + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/Forbidden.php b/vendor/sabre/dav/lib/DAV/Exception/Forbidden.php new file mode 100644 index 000000000..77df7ca9e --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/Forbidden.php @@ -0,0 +1,29 @@ +ownerDocument->createElementNS('DAV:', 'd:valid-resourcetype'); + $errorNode->appendChild($error); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/InvalidSyncToken.php b/vendor/sabre/dav/lib/DAV/Exception/InvalidSyncToken.php new file mode 100644 index 000000000..51a253b29 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/InvalidSyncToken.php @@ -0,0 +1,38 @@ +ownerDocument->createElementNS('DAV:', 'd:valid-sync-token'); + $errorNode->appendChild($error); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/LengthRequired.php b/vendor/sabre/dav/lib/DAV/Exception/LengthRequired.php new file mode 100644 index 000000000..989718558 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/LengthRequired.php @@ -0,0 +1,30 @@ +message = 'The locktoken supplied does not match any locks on this entity'; + + } + + /** + * This method allows the exception to include additional information into the WebDAV error response + * + * @param DAV\Server $server + * @param \DOMElement $errorNode + * @return void + */ + function serialize(DAV\Server $server, \DOMElement $errorNode) { + + $error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:lock-token-matches-request-uri'); + $errorNode->appendChild($error); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/Locked.php b/vendor/sabre/dav/lib/DAV/Exception/Locked.php new file mode 100644 index 000000000..8176db46e --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/Locked.php @@ -0,0 +1,72 @@ +lock = $lock; + + } + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + function getHTTPCode() { + + return 423; + + } + + /** + * This method allows the exception to include additional information into the WebDAV error response + * + * @param DAV\Server $server + * @param \DOMElement $errorNode + * @return void + */ + function serialize(DAV\Server $server, \DOMElement $errorNode) { + + if ($this->lock) { + $error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:lock-token-submitted'); + $errorNode->appendChild($error); + + $href = $errorNode->ownerDocument->createElementNS('DAV:', 'd:href'); + $href->appendChild($errorNode->ownerDocument->createTextNode($this->lock->uri)); + $error->appendChild( + $href + ); + } + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/MethodNotAllowed.php b/vendor/sabre/dav/lib/DAV/Exception/MethodNotAllowed.php new file mode 100644 index 000000000..30c1c2553 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/MethodNotAllowed.php @@ -0,0 +1,47 @@ +getAllowedMethods($server->getRequestUri()); + + return [ + 'Allow' => strtoupper(implode(', ', $methods)), + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/NotAuthenticated.php b/vendor/sabre/dav/lib/DAV/Exception/NotAuthenticated.php new file mode 100644 index 000000000..e69a60c75 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/NotAuthenticated.php @@ -0,0 +1,30 @@ +header = $header; + + } + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + function getHTTPCode() { + + return 412; + + } + + /** + * This method allows the exception to include additional information into the WebDAV error response + * + * @param DAV\Server $server + * @param \DOMElement $errorNode + * @return void + */ + function serialize(DAV\Server $server, \DOMElement $errorNode) { + + if ($this->header) { + $prop = $errorNode->ownerDocument->createElement('s:header'); + $prop->nodeValue = $this->header; + $errorNode->appendChild($prop); + } + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/ReportNotSupported.php b/vendor/sabre/dav/lib/DAV/Exception/ReportNotSupported.php new file mode 100644 index 000000000..a83695627 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/ReportNotSupported.php @@ -0,0 +1,32 @@ +ownerDocument->createElementNS('DAV:', 'd:supported-report'); + $errorNode->appendChild($error); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/RequestedRangeNotSatisfiable.php b/vendor/sabre/dav/lib/DAV/Exception/RequestedRangeNotSatisfiable.php new file mode 100644 index 000000000..c8ccfc062 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/RequestedRangeNotSatisfiable.php @@ -0,0 +1,30 @@ + + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ServiceUnavailable extends DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + function getHTTPCode() { + + return 503; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/TooManyMatches.php b/vendor/sabre/dav/lib/DAV/Exception/TooManyMatches.php new file mode 100644 index 000000000..d0f0f84e8 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/TooManyMatches.php @@ -0,0 +1,38 @@ +ownerDocument->createElementNS('DAV:', 'd:number-of-matches-within-limits'); + $errorNode->appendChild($error); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/UnsupportedMediaType.php b/vendor/sabre/dav/lib/DAV/Exception/UnsupportedMediaType.php new file mode 100644 index 000000000..f3d92842d --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/UnsupportedMediaType.php @@ -0,0 +1,30 @@ +path . '/' . $name; + file_put_contents($newPath, $data); + clearstatcache(true, $newPath); + + } + + /** + * Creates a new subdirectory + * + * @param string $name + * @return void + */ + function createDirectory($name) { + + $newPath = $this->path . '/' . $name; + mkdir($newPath); + clearstatcache(true, $newPath); + + } + + /** + * Returns a specific child node, referenced by its name + * + * This method must throw DAV\Exception\NotFound if the node does not + * exist. + * + * @param string $name + * @throws DAV\Exception\NotFound + * @return DAV\INode + */ + function getChild($name) { + + $path = $this->path . '/' . $name; + + if (!file_exists($path)) throw new DAV\Exception\NotFound('File with name ' . $path . ' could not be located'); + + if (is_dir($path)) { + + return new self($path); + + } else { + + return new File($path); + + } + + } + + /** + * Returns an array with all the child nodes + * + * @return DAV\INode[] + */ + function getChildren() { + + $nodes = []; + $iterator = new \FilesystemIterator( + $this->path, + \FilesystemIterator::CURRENT_AS_SELF + | \FilesystemIterator::SKIP_DOTS + ); + foreach ($iterator as $entry) { + + $nodes[] = $this->getChild($entry->getFilename()); + + } + return $nodes; + + } + + /** + * Checks if a child exists. + * + * @param string $name + * @return bool + */ + function childExists($name) { + + $path = $this->path . '/' . $name; + return file_exists($path); + + } + + /** + * Deletes all files in this directory, and then itself + * + * @return void + */ + function delete() { + + foreach ($this->getChildren() as $child) $child->delete(); + rmdir($this->path); + + } + + /** + * Returns available diskspace information + * + * @return array + */ + function getQuotaInfo() { + + return [ + disk_total_space($this->path) - disk_free_space($this->path), + disk_free_space($this->path) + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/FS/File.php b/vendor/sabre/dav/lib/DAV/FS/File.php new file mode 100644 index 000000000..4fc5af057 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/FS/File.php @@ -0,0 +1,95 @@ +path, $data); + clearstatcache(true, $this->path); + + } + + /** + * Returns the data + * + * @return resource + */ + function get() { + + return fopen($this->path, 'r'); + + } + + /** + * Delete the current file + * + * @return void + */ + function delete() { + + unlink($this->path); + + } + + /** + * Returns the size of the node, in bytes + * + * @return int + */ + function getSize() { + + return filesize($this->path); + + } + + /** + * Returns the ETag for a file + * + * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. + * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. + * + * Return null if the ETag can not effectively be determined + * + * @return mixed + */ + function getETag() { + + return '"' . sha1( + fileinode($this->path) . + filesize($this->path) . + filemtime($this->path) + ) . '"'; + + } + + /** + * Returns the mime-type for a file + * + * If null is returned, we'll assume application/octet-stream + * + * @return mixed + */ + function getContentType() { + + return null; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/FS/Node.php b/vendor/sabre/dav/lib/DAV/FS/Node.php new file mode 100644 index 000000000..831c11911 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/FS/Node.php @@ -0,0 +1,80 @@ +path = $path; + + } + + + + /** + * Returns the name of the node + * + * @return string + */ + function getName() { + + list(, $name) = URLUtil::splitPath($this->path); + return $name; + + } + + /** + * Renames the node + * + * @param string $name The new name + * @return void + */ + function setName($name) { + + list($parentPath, ) = URLUtil::splitPath($this->path); + list(, $newName) = URLUtil::splitPath($name); + + $newPath = $parentPath . '/' . $newName; + rename($this->path, $newPath); + + $this->path = $newPath; + + } + + /** + * Returns the last modification time, as a unix timestamp + * + * @return int + */ + function getLastModified() { + + return filemtime($this->path); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/FSExt/Directory.php b/vendor/sabre/dav/lib/DAV/FSExt/Directory.php new file mode 100644 index 000000000..648079e26 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/FSExt/Directory.php @@ -0,0 +1,219 @@ +path . '/' . $name; + file_put_contents($newPath, $data); + clearstatcache(true, $newPath); + + return '"' . sha1( + fileinode($newPath) . + filesize($newPath) . + filemtime($newPath) + ) . '"'; + + } + + /** + * Creates a new subdirectory + * + * @param string $name + * @return void + */ + function createDirectory($name) { + + // We're not allowing dots + if ($name == '.' || $name == '..') throw new DAV\Exception\Forbidden('Permission denied to . and ..'); + $newPath = $this->path . '/' . $name; + mkdir($newPath); + clearstatcache(true, $newPath); + + } + + /** + * Returns a specific child node, referenced by its name + * + * This method must throw Sabre\DAV\Exception\NotFound if the node does not + * exist. + * + * @param string $name + * @throws DAV\Exception\NotFound + * @return DAV\INode + */ + function getChild($name) { + + $path = $this->path . '/' . $name; + + if (!file_exists($path)) throw new DAV\Exception\NotFound('File could not be located'); + if ($name == '.' || $name == '..') throw new DAV\Exception\Forbidden('Permission denied to . and ..'); + + if (is_dir($path)) { + + return new self($path); + + } else { + + return new File($path); + + } + + } + + /** + * Checks if a child exists. + * + * @param string $name + * @return bool + */ + function childExists($name) { + + if ($name == '.' || $name == '..') + throw new DAV\Exception\Forbidden('Permission denied to . and ..'); + + $path = $this->path . '/' . $name; + return file_exists($path); + + } + + /** + * Returns an array with all the child nodes + * + * @return DAV\INode[] + */ + function getChildren() { + + $nodes = []; + $iterator = new \FilesystemIterator( + $this->path, + \FilesystemIterator::CURRENT_AS_SELF + | \FilesystemIterator::SKIP_DOTS + ); + + foreach ($iterator as $entry) { + + $node = $entry->getFilename(); + + if ($node === '.sabredav') + continue; + + $nodes[] = $this->getChild($node); + + } + return $nodes; + + } + + /** + * Deletes all files in this directory, and then itself + * + * @return bool + */ + function delete() { + + // Deleting all children + foreach ($this->getChildren() as $child) $child->delete(); + + // Removing resource info, if its still around + if (file_exists($this->path . '/.sabredav')) unlink($this->path . '/.sabredav'); + + // Removing the directory itself + rmdir($this->path); + + return true; + + } + + /** + * Returns available diskspace information + * + * @return array + */ + function getQuotaInfo() { + + $total = disk_total_space(realpath($this->path)); + $free = disk_free_space(realpath($this->path)); + + return [ + $total - $free, + $free + ]; + } + + /** + * Moves a node into this collection. + * + * It is up to the implementors to: + * 1. Create the new resource. + * 2. Remove the old resource. + * 3. Transfer any properties or other data. + * + * Generally you should make very sure that your collection can easily move + * the move. + * + * If you don't, just return false, which will trigger sabre/dav to handle + * the move itself. If you return true from this function, the assumption + * is that the move was successful. + * + * @param string $targetName New local file/collection name. + * @param string $sourcePath Full path to source node + * @param DAV\INode $sourceNode Source node itself + * @return bool + */ + function moveInto($targetName, $sourcePath, DAV\INode $sourceNode) { + + // We only support FSExt\Directory or FSExt\File objects, so + // anything else we want to quickly reject. + if (!$sourceNode instanceof self && !$sourceNode instanceof File) { + return false; + } + + // PHP allows us to access protected properties from other objects, as + // long as they are defined in a class that has a shared inheritence + // with the current class. + rename($sourceNode->path, $this->path . '/' . $targetName); + + return true; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/FSExt/File.php b/vendor/sabre/dav/lib/DAV/FSExt/File.php new file mode 100644 index 000000000..eb5ae19fe --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/FSExt/File.php @@ -0,0 +1,152 @@ +path, $data); + clearstatcache(true, $this->path); + return $this->getETag(); + + } + + /** + * Updates the file based on a range specification. + * + * The first argument is the data, which is either a readable stream + * resource or a string. + * + * The second argument is the type of update we're doing. + * This is either: + * * 1. append + * * 2. update based on a start byte + * * 3. update based on an end byte + *; + * The third argument is the start or end byte. + * + * After a successful put operation, you may choose to return an ETag. The + * ETAG must always be surrounded by double-quotes. These quotes must + * appear in the actual string you're returning. + * + * Clients may use the ETag from a PUT request to later on make sure that + * when they update the file, the contents haven't changed in the mean + * time. + * + * @param resource|string $data + * @param int $rangeType + * @param int $offset + * @return string|null + */ + function patch($data, $rangeType, $offset = null) { + + switch ($rangeType) { + case 1 : + $f = fopen($this->path, 'a'); + break; + case 2 : + $f = fopen($this->path, 'c'); + fseek($f, $offset); + break; + case 3 : + $f = fopen($this->path, 'c'); + fseek($f, $offset, SEEK_END); + break; + } + if (is_string($data)) { + fwrite($f, $data); + } else { + stream_copy_to_stream($data, $f); + } + fclose($f); + clearstatcache(true, $this->path); + return $this->getETag(); + + } + + /** + * Returns the data + * + * @return resource + */ + function get() { + + return fopen($this->path, 'r'); + + } + + /** + * Delete the current file + * + * @return bool + */ + function delete() { + + return unlink($this->path); + + } + + /** + * Returns the ETag for a file + * + * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. + * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. + * + * Return null if the ETag can not effectively be determined + * + * @return string|null + */ + function getETag() { + + return '"' . sha1( + fileinode($this->path) . + filesize($this->path) . + filemtime($this->path) + ) . '"'; + + } + + /** + * Returns the mime-type for a file + * + * If null is returned, we'll assume application/octet-stream + * + * @return string|null + */ + function getContentType() { + + return null; + + } + + /** + * Returns the size of the file, in bytes + * + * @return int + */ + function getSize() { + + return filesize($this->path); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/File.php b/vendor/sabre/dav/lib/DAV/File.php new file mode 100644 index 000000000..e0a0391db --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/File.php @@ -0,0 +1,84 @@ +locksFile = $locksFile; + + } + + /** + * Returns a list of Sabre\DAV\Locks\LockInfo objects + * + * This method should return all the locks for a particular uri, including + * locks that might be set on a parent uri. + * + * If returnChildLocks is set to true, this method should also look for + * any locks in the subtree of the uri for locks. + * + * @param string $uri + * @param bool $returnChildLocks + * @return array + */ + function getLocks($uri, $returnChildLocks) { + + $newLocks = []; + + $locks = $this->getData(); + + foreach ($locks as $lock) { + + if ($lock->uri === $uri || + //deep locks on parents + ($lock->depth != 0 && strpos($uri, $lock->uri . '/') === 0) || + + // locks on children + ($returnChildLocks && (strpos($lock->uri, $uri . '/') === 0))) { + + $newLocks[] = $lock; + + } + + } + + // Checking if we can remove any of these locks + foreach ($newLocks as $k => $lock) { + if (time() > $lock->timeout + $lock->created) unset($newLocks[$k]); + } + return $newLocks; + + } + + /** + * Locks a uri + * + * @param string $uri + * @param LockInfo $lockInfo + * @return bool + */ + function lock($uri, LockInfo $lockInfo) { + + // We're making the lock timeout 30 minutes + $lockInfo->timeout = 1800; + $lockInfo->created = time(); + $lockInfo->uri = $uri; + + $locks = $this->getData(); + + foreach ($locks as $k => $lock) { + if ( + ($lock->token == $lockInfo->token) || + (time() > $lock->timeout + $lock->created) + ) { + unset($locks[$k]); + } + } + $locks[] = $lockInfo; + $this->putData($locks); + return true; + + } + + /** + * Removes a lock from a uri + * + * @param string $uri + * @param LockInfo $lockInfo + * @return bool + */ + function unlock($uri, LockInfo $lockInfo) { + + $locks = $this->getData(); + foreach ($locks as $k => $lock) { + + if ($lock->token == $lockInfo->token) { + + unset($locks[$k]); + $this->putData($locks); + return true; + + } + } + return false; + + } + + /** + * Loads the lockdata from the filesystem. + * + * @return array + */ + protected function getData() { + + if (!file_exists($this->locksFile)) return []; + + // opening up the file, and creating a shared lock + $handle = fopen($this->locksFile, 'r'); + flock($handle, LOCK_SH); + + // Reading data until the eof + $data = stream_get_contents($handle); + + // We're all good + flock($handle, LOCK_UN); + fclose($handle); + + // Unserializing and checking if the resource file contains data for this file + $data = unserialize($data); + if (!$data) return []; + return $data; + + } + + /** + * Saves the lockdata + * + * @param array $newData + * @return void + */ + protected function putData(array $newData) { + + // opening up the file, and creating an exclusive lock + $handle = fopen($this->locksFile, 'a+'); + flock($handle, LOCK_EX); + + // We can only truncate and rewind once the lock is acquired. + ftruncate($handle, 0); + rewind($handle); + + fwrite($handle, serialize($newData)); + flock($handle, LOCK_UN); + fclose($handle); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php b/vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php new file mode 100644 index 000000000..a01d9bae4 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php @@ -0,0 +1,180 @@ +pdo = $pdo; + + } + + /** + * Returns a list of Sabre\DAV\Locks\LockInfo objects + * + * This method should return all the locks for a particular uri, including + * locks that might be set on a parent uri. + * + * If returnChildLocks is set to true, this method should also look for + * any locks in the subtree of the uri for locks. + * + * @param string $uri + * @param bool $returnChildLocks + * @return array + */ + function getLocks($uri, $returnChildLocks) { + + // NOTE: the following 10 lines or so could be easily replaced by + // pure sql. MySQL's non-standard string concatenation prevents us + // from doing this though. + $query = 'SELECT owner, token, timeout, created, scope, depth, uri FROM ' . $this->tableName . ' WHERE (created > (? - timeout)) AND ((uri = ?)'; + $params = [time(),$uri]; + + // We need to check locks for every part in the uri. + $uriParts = explode('/', $uri); + + // We already covered the last part of the uri + array_pop($uriParts); + + $currentPath = ''; + + foreach ($uriParts as $part) { + + if ($currentPath) $currentPath .= '/'; + $currentPath .= $part; + + $query .= ' OR (depth!=0 AND uri = ?)'; + $params[] = $currentPath; + + } + + if ($returnChildLocks) { + + $query .= ' OR (uri LIKE ?)'; + $params[] = $uri . '/%'; + + } + $query .= ')'; + + $stmt = $this->pdo->prepare($query); + $stmt->execute($params); + $result = $stmt->fetchAll(); + + $lockList = []; + foreach ($result as $row) { + + $lockInfo = new LockInfo(); + $lockInfo->owner = $row['owner']; + $lockInfo->token = $row['token']; + $lockInfo->timeout = $row['timeout']; + $lockInfo->created = $row['created']; + $lockInfo->scope = $row['scope']; + $lockInfo->depth = $row['depth']; + $lockInfo->uri = $row['uri']; + $lockList[] = $lockInfo; + + } + + return $lockList; + + } + + /** + * Locks a uri + * + * @param string $uri + * @param LockInfo $lockInfo + * @return bool + */ + function lock($uri, LockInfo $lockInfo) { + + // We're making the lock timeout 30 minutes + $lockInfo->timeout = 30 * 60; + $lockInfo->created = time(); + $lockInfo->uri = $uri; + + $locks = $this->getLocks($uri, false); + $exists = false; + foreach ($locks as $lock) { + if ($lock->token == $lockInfo->token) $exists = true; + } + + if ($exists) { + $stmt = $this->pdo->prepare('UPDATE ' . $this->tableName . ' SET owner = ?, timeout = ?, scope = ?, depth = ?, uri = ?, created = ? WHERE token = ?'); + $stmt->execute([ + $lockInfo->owner, + $lockInfo->timeout, + $lockInfo->scope, + $lockInfo->depth, + $uri, + $lockInfo->created, + $lockInfo->token + ]); + } else { + $stmt = $this->pdo->prepare('INSERT INTO ' . $this->tableName . ' (owner,timeout,scope,depth,uri,created,token) VALUES (?,?,?,?,?,?,?)'); + $stmt->execute([ + $lockInfo->owner, + $lockInfo->timeout, + $lockInfo->scope, + $lockInfo->depth, + $uri, + $lockInfo->created, + $lockInfo->token + ]); + } + + return true; + + } + + + + /** + * Removes a lock from a uri + * + * @param string $uri + * @param LockInfo $lockInfo + * @return bool + */ + function unlock($uri, LockInfo $lockInfo) { + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->tableName . ' WHERE uri = ? AND token = ?'); + $stmt->execute([$uri, $lockInfo->token]); + + return $stmt->rowCount() === 1; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Locks/LockInfo.php b/vendor/sabre/dav/lib/DAV/Locks/LockInfo.php new file mode 100644 index 000000000..2c8cca0fe --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Locks/LockInfo.php @@ -0,0 +1,80 @@ +addPlugin($lockPlugin); + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Plugin extends DAV\ServerPlugin { + + /** + * locksBackend + * + * @var Backend\Backend\Interface + */ + protected $locksBackend; + + /** + * server + * + * @var Sabre\DAV\Server + */ + protected $server; + + /** + * __construct + * + * @param Backend\BackendInterface $locksBackend + */ + function __construct(Backend\BackendInterface $locksBackend) { + + $this->locksBackend = $locksBackend; + + } + + /** + * Initializes the plugin + * + * This method is automatically called by the Server class after addPlugin. + * + * @param DAV\Server $server + * @return void + */ + function initialize(DAV\Server $server) { + + $this->server = $server; + + $this->server->xml->elementMap['{DAV:}lockinfo'] = 'Sabre\\DAV\\Xml\\Request\\Lock'; + + $server->on('method:LOCK', [$this, 'httpLock']); + $server->on('method:UNLOCK', [$this, 'httpUnlock']); + $server->on('validateTokens', [$this, 'validateTokens']); + $server->on('propFind', [$this, 'propFind']); + $server->on('afterUnbind', [$this, 'afterUnbind']); + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using Sabre\DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'locks'; + + } + + /** + * This method is called after most properties have been found + * it allows us to add in any Lock-related properties + * + * @param DAV\PropFind $propFind + * @param DAV\INode $node + * @return void + */ + function propFind(DAV\PropFind $propFind, DAV\INode $node) { + + $propFind->handle('{DAV:}supportedlock', function() { + return new DAV\Xml\Property\SupportedLock(); + }); + $propFind->handle('{DAV:}lockdiscovery', function() use ($propFind) { + return new DAV\Xml\Property\LockDiscovery( + $this->getLocks($propFind->getPath()) + ); + }); + + } + + /** + * Use this method to tell the server this plugin defines additional + * HTTP methods. + * + * This method is passed a uri. It should only return HTTP methods that are + * available for the specified uri. + * + * @param string $uri + * @return array + */ + function getHTTPMethods($uri) { + + return ['LOCK','UNLOCK']; + + } + + /** + * Returns a list of features for the HTTP OPTIONS Dav: header. + * + * In this case this is only the number 2. The 2 in the Dav: header + * indicates the server supports locks. + * + * @return array + */ + function getFeatures() { + + return [2]; + + } + + /** + * Returns all lock information on a particular uri + * + * This function should return an array with Sabre\DAV\Locks\LockInfo objects. If there are no locks on a file, return an empty array. + * + * Additionally there is also the possibility of locks on parent nodes, so we'll need to traverse every part of the tree + * If the $returnChildLocks argument is set to true, we'll also traverse all the children of the object + * for any possible locks and return those as well. + * + * @param string $uri + * @param bool $returnChildLocks + * @return array + */ + function getLocks($uri, $returnChildLocks = false) { + + return $this->locksBackend->getLocks($uri, $returnChildLocks); + + } + + /** + * Locks an uri + * + * The WebDAV lock request can be operated to either create a new lock on a file, or to refresh an existing lock + * If a new lock is created, a full XML body should be supplied, containing information about the lock such as the type + * of lock (shared or exclusive) and the owner of the lock + * + * If a lock is to be refreshed, no body should be supplied and there should be a valid If header containing the lock + * + * Additionally, a lock can be requested for a non-existent file. In these case we're obligated to create an empty file as per RFC4918:S7.3 + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpLock(RequestInterface $request, ResponseInterface $response) { + + $uri = $request->getPath(); + + $existingLocks = $this->getLocks($uri); + + if ($body = $request->getBodyAsString()) { + // This is a new lock request + + $existingLock = null; + // Checking if there's already non-shared locks on the uri. + foreach ($existingLocks as $existingLock) { + if ($existingLock->scope === LockInfo::EXCLUSIVE) { + throw new DAV\Exception\ConflictingLock($existingLock); + } + } + + $lockInfo = $this->parseLockRequest($body); + $lockInfo->depth = $this->server->getHTTPDepth(); + $lockInfo->uri = $uri; + if ($existingLock && $lockInfo->scope != LockInfo::SHARED) + throw new DAV\Exception\ConflictingLock($existingLock); + + } else { + + // Gonna check if this was a lock refresh. + $existingLocks = $this->getLocks($uri); + $conditions = $this->server->getIfConditions($request); + $found = null; + + foreach ($existingLocks as $existingLock) { + foreach ($conditions as $condition) { + foreach ($condition['tokens'] as $token) { + if ($token['token'] === 'opaquelocktoken:' . $existingLock->token) { + $found = $existingLock; + break 3; + } + } + } + } + + // If none were found, this request is in error. + if (is_null($found)) { + if ($existingLocks) { + throw new DAV\Exception\Locked(reset($existingLocks)); + } else { + throw new DAV\Exception\BadRequest('An xml body is required for lock requests'); + } + + } + + // This must have been a lock refresh + $lockInfo = $found; + + // The resource could have been locked through another uri. + if ($uri != $lockInfo->uri) $uri = $lockInfo->uri; + + } + + if ($timeout = $this->getTimeoutHeader()) $lockInfo->timeout = $timeout; + + $newFile = false; + + // If we got this far.. we should go check if this node actually exists. If this is not the case, we need to create it first + try { + $this->server->tree->getNodeForPath($uri); + + // We need to call the beforeWriteContent event for RFC3744 + // Edit: looks like this is not used, and causing problems now. + // + // See Issue 222 + // $this->server->emit('beforeWriteContent',array($uri)); + + } catch (DAV\Exception\NotFound $e) { + + // It didn't, lets create it + $this->server->createFile($uri, fopen('php://memory', 'r')); + $newFile = true; + + } + + $this->lockNode($uri, $lockInfo); + + $response->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $response->setHeader('Lock-Token', 'token . '>'); + $response->setStatus($newFile ? 201 : 200); + $response->setBody($this->generateLockResponse($lockInfo)); + + // Returning false will interupt the event chain and mark this method + // as 'handled'. + return false; + + } + + /** + * Unlocks a uri + * + * This WebDAV method allows you to remove a lock from a node. The client should provide a valid locktoken through the Lock-token http header + * The server should return 204 (No content) on success + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function httpUnlock(RequestInterface $request, ResponseInterface $response) { + + $lockToken = $request->getHeader('Lock-Token'); + + // If the locktoken header is not supplied, we need to throw a bad request exception + if (!$lockToken) throw new DAV\Exception\BadRequest('No lock token was supplied'); + + $path = $request->getPath(); + $locks = $this->getLocks($path); + + // Windows sometimes forgets to include < and > in the Lock-Token + // header + if ($lockToken[0] !== '<') $lockToken = '<' . $lockToken . '>'; + + foreach ($locks as $lock) { + + if ('token . '>' == $lockToken) { + + $this->unlockNode($path, $lock); + $response->setHeader('Content-Length', '0'); + $response->setStatus(204); + + // Returning false will break the method chain, and mark the + // method as 'handled'. + return false; + + } + + } + + // If we got here, it means the locktoken was invalid + throw new DAV\Exception\LockTokenMatchesRequestUri(); + + } + + /** + * This method is called after a node is deleted. + * + * We use this event to clean up any locks that still exist on the node. + * + * @param string $path + * @return void + */ + function afterUnbind($path) { + + $locks = $this->getLocks($path, $includeChildren = true); + foreach ($locks as $lock) { + $this->unlockNode($path, $lock); + } + + } + + /** + * Locks a uri + * + * All the locking information is supplied in the lockInfo object. The object has a suggested timeout, but this can be safely ignored + * It is important that if the existing timeout is ignored, the property is overwritten, as this needs to be sent back to the client + * + * @param string $uri + * @param LockInfo $lockInfo + * @return bool + */ + function lockNode($uri, LockInfo $lockInfo) { + + if (!$this->server->emit('beforeLock', [$uri, $lockInfo])) return; + return $this->locksBackend->lock($uri, $lockInfo); + + } + + /** + * Unlocks a uri + * + * This method removes a lock from a uri. It is assumed all the supplied information is correct and verified + * + * @param string $uri + * @param LockInfo $lockInfo + * @return bool + */ + function unlockNode($uri, LockInfo $lockInfo) { + + if (!$this->server->emit('beforeUnlock', [$uri, $lockInfo])) return; + return $this->locksBackend->unlock($uri, $lockInfo); + + } + + + /** + * Returns the contents of the HTTP Timeout header. + * + * The method formats the header into an integer. + * + * @return int + */ + function getTimeoutHeader() { + + $header = $this->server->httpRequest->getHeader('Timeout'); + + if ($header) { + + if (stripos($header, 'second-') === 0) $header = (int)(substr($header, 7)); + elseif (stripos($header, 'infinite') === 0) $header = LockInfo::TIMEOUT_INFINITE; + else throw new DAV\Exception\BadRequest('Invalid HTTP timeout header'); + + } else { + + $header = 0; + + } + + return $header; + + } + + /** + * Generates the response for successful LOCK requests + * + * @param LockInfo $lockInfo + * @return string + */ + protected function generateLockResponse(LockInfo $lockInfo) { + + return $this->server->xml->write('{DAV:}prop', [ + '{DAV:}lockdiscovery' => + new DAV\Xml\Property\LockDiscovery([$lockInfo]) + ]); + } + + /** + * The validateTokens event is triggered before every request. + * + * It's a moment where this plugin can check all the supplied lock tokens + * in the If: header, and check if they are valid. + * + * In addition, it will also ensure that it checks any missing lokens that + * must be present in the request, and reject requests without the proper + * tokens. + * + * @param RequestInterface $request + * @param mixed $conditions + * @return void + */ + function validateTokens(RequestInterface $request, &$conditions) { + + // First we need to gather a list of locks that must be satisfied. + $mustLocks = []; + $method = $request->getMethod(); + + // Methods not in that list are operations that doesn't alter any + // resources, and we don't need to check the lock-states for. + switch ($method) { + + case 'DELETE' : + $mustLocks = array_merge($mustLocks, $this->getLocks( + $request->getPath(), + true + )); + break; + case 'MKCOL' : + case 'MKCALENDAR' : + case 'PROPPATCH' : + case 'PUT' : + case 'PATCH' : + $mustLocks = array_merge($mustLocks, $this->getLocks( + $request->getPath(), + false + )); + break; + case 'MOVE' : + $mustLocks = array_merge($mustLocks, $this->getLocks( + $request->getPath(), + true + )); + $mustLocks = array_merge($mustLocks, $this->getLocks( + $this->server->calculateUri($request->getHeader('Destination')), + false + )); + break; + case 'COPY' : + $mustLocks = array_merge($mustLocks, $this->getLocks( + $this->server->calculateUri($request->getHeader('Destination')), + false + )); + break; + case 'LOCK' : + //Temporary measure.. figure out later why this is needed + // Here we basically ignore all incoming tokens... + foreach ($conditions as $ii => $condition) { + foreach ($condition['tokens'] as $jj => $token) { + $conditions[$ii]['tokens'][$jj]['validToken'] = true; + } + } + return; + + } + + // It's possible that there's identical locks, because of shared + // parents. We're removing the duplicates here. + $tmp = []; + foreach ($mustLocks as $lock) $tmp[$lock->token] = $lock; + $mustLocks = array_values($tmp); + + foreach ($conditions as $kk => $condition) { + + foreach ($condition['tokens'] as $ii => $token) { + + // Lock tokens always start with opaquelocktoken: + if (substr($token['token'], 0, 16) !== 'opaquelocktoken:') { + continue; + } + + $checkToken = substr($token['token'], 16); + // Looping through our list with locks. + foreach ($mustLocks as $jj => $mustLock) { + + if ($mustLock->token == $checkToken) { + + // We have a match! + // Removing this one from mustlocks + unset($mustLocks[$jj]); + + // Marking the condition as valid. + $conditions[$kk]['tokens'][$ii]['validToken'] = true; + + // Advancing to the next token + continue 2; + + } + + } + + // If we got here, it means that there was a + // lock-token, but it was not in 'mustLocks'. + // + // This is an edge-case, as it could mean that token + // was specified with a url that was not 'required' to + // check. So we're doing one extra lookup to make sure + // we really don't know this token. + // + // This also gets triggered when the user specified a + // lock-token that was expired. + $oddLocks = $this->getLocks($condition['uri']); + foreach ($oddLocks as $oddLock) { + + if ($oddLock->token === $checkToken) { + + // We have a hit! + $conditions[$kk]['tokens'][$ii]['validToken'] = true; + continue 2; + + } + } + + // If we get all the way here, the lock-token was + // really unknown. + + + } + + } + + // If there's any locks left in the 'mustLocks' array, it means that + // the resource was locked and we must block it. + if ($mustLocks) { + + throw new DAV\Exception\Locked(reset($mustLocks)); + + } + + } + + /** + * Parses a webdav lock xml body, and returns a new Sabre\DAV\Locks\LockInfo object + * + * @param string $body + * @return LockInfo + */ + protected function parseLockRequest($body) { + + $result = $this->server->xml->expect( + '{DAV:}lockinfo', + $body + ); + + $lockInfo = new LockInfo(); + + $lockInfo->owner = $result->owner; + $lockInfo->token = DAV\UUIDUtil::getUUID(); + $lockInfo->scope = $result->scope; + + return $lockInfo; + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'The locks plugin turns this server into a class-2 WebDAV server and adds support for LOCK and UNLOCK', + 'link' => 'http://sabre.io/dav/locks/', + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/MkCol.php b/vendor/sabre/dav/lib/DAV/MkCol.php new file mode 100644 index 000000000..c79055418 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/MkCol.php @@ -0,0 +1,71 @@ +resourceType = $resourceType; + parent::__construct($mutations); + + } + + /** + * Returns the resourcetype of the new collection. + * + * @return string[] + */ + function getResourceType() { + + return $this->resourceType; + + } + + /** + * Returns true or false if the MKCOL operation has at least the specified + * resource type. + * + * If the resourcetype is specified as an array, all resourcetypes are + * checked. + * + * @param string|string[] $resourceType + */ + function hasResourceType($resourceType) { + + return count(array_diff((array)$resourceType, $this->resourceType)) === 0; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Mount/Plugin.php b/vendor/sabre/dav/lib/DAV/Mount/Plugin.php new file mode 100644 index 000000000..8e06acb9f --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Mount/Plugin.php @@ -0,0 +1,86 @@ +server = $server; + $this->server->on('method:GET', [$this, 'httpGet'], 90); + + } + + /** + * 'beforeMethod' event handles. This event handles intercepts GET requests ending + * with ?mount + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpGet(RequestInterface $request, ResponseInterface $response) { + + $queryParams = $request->getQueryParameters(); + if (!array_key_exists('mount', $queryParams)) return; + + $currentUri = $request->getAbsoluteUrl(); + + // Stripping off everything after the ? + list($currentUri) = explode('?', $currentUri); + + $this->davMount($response, $currentUri); + + // Returning false to break the event chain + return false; + + } + + /** + * Generates the davmount response + * + * @param ResponseInterface $response + * @param string $uri absolute uri + * @return void + */ + function davMount(ResponseInterface $response, $uri) { + + $response->setStatus(200); + $response->setHeader('Content-Type', 'application/davmount+xml'); + ob_start(); + echo '', "\n"; + echo "\n"; + echo " ", htmlspecialchars($uri, ENT_NOQUOTES, 'UTF-8'), "\n"; + echo ""; + $response->setBody(ob_get_clean()); + + } + + +} diff --git a/vendor/sabre/dav/lib/DAV/Node.php b/vendor/sabre/dav/lib/DAV/Node.php new file mode 100644 index 000000000..ba270e8f9 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Node.php @@ -0,0 +1,54 @@ +addPlugin($patchPlugin); + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Jean-Tiare LE BIGOT (http://www.jtlebi.fr/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Plugin extends DAV\ServerPlugin { + + const RANGE_APPEND = 1; + const RANGE_START = 2; + const RANGE_END = 3; + + /** + * Reference to server + * + * @var Sabre\DAV\Server + */ + protected $server; + + /** + * Initializes the plugin + * + * This method is automatically called by the Server class after addPlugin. + * + * @param DAV\Server $server + * @return void + */ + function initialize(DAV\Server $server) { + + $this->server = $server; + $server->on('method:PATCH', [$this, 'httpPatch']); + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'partialupdate'; + + } + + /** + * Use this method to tell the server this plugin defines additional + * HTTP methods. + * + * This method is passed a uri. It should only return HTTP methods that are + * available for the specified uri. + * + * We claim to support PATCH method (partirl update) if and only if + * - the node exist + * - the node implements our partial update interface + * + * @param string $uri + * @return array + */ + function getHTTPMethods($uri) { + + $tree = $this->server->tree; + + if ($tree->nodeExists($uri)) { + $node = $tree->getNodeForPath($uri); + if ($node instanceof IPatchSupport) { + return ['PATCH']; + } + } + return []; + + } + + /** + * Returns a list of features for the HTTP OPTIONS Dav: header. + * + * @return array + */ + function getFeatures() { + + return ['sabredav-partialupdate']; + + } + + /** + * Patch an uri + * + * The WebDAV patch request can be used to modify only a part of an + * existing resource. If the resource does not exist yet and the first + * offset is not 0, the request fails + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function httpPatch(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + // Get the node. Will throw a 404 if not found + $node = $this->server->tree->getNodeForPath($path); + if (!$node instanceof IPatchSupport) { + throw new DAV\Exception\MethodNotAllowed('The target resource does not support the PATCH method.'); + } + + $range = $this->getHTTPUpdateRange($request); + + if (!$range) { + throw new DAV\Exception\BadRequest('No valid "X-Update-Range" found in the headers'); + } + + $contentType = strtolower( + $request->getHeader('Content-Type') + ); + + if ($contentType != 'application/x-sabredav-partialupdate') { + throw new DAV\Exception\UnsupportedMediaType('Unknown Content-Type header "' . $contentType . '"'); + } + + $len = $this->server->httpRequest->getHeader('Content-Length'); + if (!$len) throw new DAV\Exception\LengthRequired('A Content-Length header is required'); + + switch ($range[0]) { + case self::RANGE_START : + // Calculate the end-range if it doesn't exist. + if (!$range[2]) { + $range[2] = $range[1] + $len - 1; + } else { + if ($range[2] < $range[1]) { + throw new DAV\Exception\RequestedRangeNotSatisfiable('The end offset (' . $range[2] . ') is lower than the start offset (' . $range[1] . ')'); + } + if ($range[2] - $range[1] + 1 != $len) { + throw new DAV\Exception\RequestedRangeNotSatisfiable('Actual data length (' . $len . ') is not consistent with begin (' . $range[1] . ') and end (' . $range[2] . ') offsets'); + } + } + break; + } + + if (!$this->server->emit('beforeWriteContent', [$path, $node, null])) + return; + + $body = $this->server->httpRequest->getBody(); + + + $etag = $node->patch($body, $range[0], isset($range[1]) ? $range[1] : null); + + $this->server->emit('afterWriteContent', [$path, $node]); + + $response->setHeader('Content-Length', '0'); + if ($etag) $response->setHeader('ETag', $etag); + $response->setStatus(204); + + // Breaks the event chain + return false; + + } + + /** + * Returns the HTTP custom range update header + * + * This method returns null if there is no well-formed HTTP range request + * header. It returns array(1) if it was an append request, array(2, + * $start, $end) if it's a start and end range, lastly it's array(3, + * $endoffset) if the offset was negative, and should be calculated from + * the end of the file. + * + * Examples: + * + * null - invalid + * [1] - append + * [2,10,15] - update bytes 10, 11, 12, 13, 14, 15 + * [2,10,null] - update bytes 10 until the end of the patch body + * [3,-5] - update from 5 bytes from the end of the file. + * + * @param RequestInterface $request + * @return array|null + */ + function getHTTPUpdateRange(RequestInterface $request) { + + $range = $request->getHeader('X-Update-Range'); + if (is_null($range)) return null; + + // Matching "Range: bytes=1234-5678: both numbers are optional + + if (!preg_match('/^(append)|(?:bytes=([0-9]+)-([0-9]*))|(?:bytes=(-[0-9]+))$/i', $range, $matches)) return null; + + if ($matches[1] === 'append') { + return [self::RANGE_APPEND]; + } elseif (strlen($matches[2]) > 0) { + return [self::RANGE_START, $matches[2], $matches[3] ?: null]; + } else { + return [self::RANGE_END, $matches[4]]; + } + + } +} diff --git a/vendor/sabre/dav/lib/DAV/PropFind.php b/vendor/sabre/dav/lib/DAV/PropFind.php new file mode 100644 index 000000000..8ae6b6cfd --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/PropFind.php @@ -0,0 +1,347 @@ +path = $path; + $this->properties = $properties; + $this->depth = $depth; + $this->requestType = $requestType; + + if ($requestType === self::ALLPROPS) { + $this->properties = [ + '{DAV:}getlastmodified', + '{DAV:}getcontentlength', + '{DAV:}resourcetype', + '{DAV:}quota-used-bytes', + '{DAV:}quota-available-bytes', + '{DAV:}getetag', + '{DAV:}getcontenttype', + ]; + } + + foreach ($this->properties as $propertyName) { + + // Seeding properties with 404's. + $this->result[$propertyName] = [404, null]; + + } + $this->itemsLeft = count($this->result); + + } + + /** + * Handles a specific property. + * + * This method checks wether the specified property was requested in this + * PROPFIND request, and if so, it will call the callback and use the + * return value for it's value. + * + * Example: + * + * $propFind->handle('{DAV:}displayname', function() { + * return 'hello'; + * }); + * + * Note that handle will only work the first time. If null is returned, the + * value is ignored. + * + * It's also possible to not pass a callback, but immediately pass a value + * + * @param string $propertyName + * @param mixed $valueOrCallBack + * @return void + */ + function handle($propertyName, $valueOrCallBack) { + + if ($this->itemsLeft && isset($this->result[$propertyName]) && $this->result[$propertyName][0] === 404) { + if (is_callable($valueOrCallBack)) { + $value = $valueOrCallBack(); + } else { + $value = $valueOrCallBack; + } + if (!is_null($value)) { + $this->itemsLeft--; + $this->result[$propertyName] = [200, $value]; + } + } + + } + + /** + * Sets the value of the property + * + * If status is not supplied, the status will default to 200 for non-null + * properties, and 404 for null properties. + * + * @param string $propertyName + * @param mixed $value + * @param int $status + * @return void + */ + function set($propertyName, $value, $status = null) { + + if (is_null($status)) { + $status = is_null($value) ? 404 : 200; + } + // If this is an ALLPROPS request and the property is + // unknown, add it to the result; else ignore it: + if (!isset($this->result[$propertyName])) { + if ($this->requestType === self::ALLPROPS) { + $this->result[$propertyName] = [$status, $value]; + } + return; + } + if ($status !== 404 && $this->result[$propertyName][0] === 404) { + $this->itemsLeft--; + } elseif ($status === 404 && $this->result[$propertyName][0] !== 404) { + $this->itemsLeft++; + } + $this->result[$propertyName] = [$status, $value]; + + } + + /** + * Returns the current value for a property. + * + * @param string $propertyName + * @return mixed + */ + function get($propertyName) { + + return isset($this->result[$propertyName]) ? $this->result[$propertyName][1] : null; + + } + + /** + * Returns the current status code for a property name. + * + * If the property does not appear in the list of requested properties, + * null will be returned. + * + * @param string $propertyName + * @return int|null + */ + function getStatus($propertyName) { + + return isset($this->result[$propertyName]) ? $this->result[$propertyName][0] : null; + + } + + /** + * Updates the path for this PROPFIND. + * + * @param string $path + * @return void + */ + function setPath($path) { + + $this->path = $path; + + } + + /** + * Returns the path this PROPFIND request is for. + * + * @return string + */ + function getPath() { + + return $this->path; + + } + + /** + * Returns the depth of this propfind request. + * + * @return int + */ + function getDepth() { + + return $this->depth; + + } + + /** + * Updates the depth of this propfind request. + * + * @param int $depth + * @return void + */ + function setDepth($depth) { + + $this->depth = $depth; + + } + + /** + * Returns all propertynames that have a 404 status, and thus don't have a + * value yet. + * + * @return array + */ + function get404Properties() { + + if ($this->itemsLeft === 0) { + return []; + } + $result = []; + foreach ($this->result as $propertyName => $stuff) { + if ($stuff[0] === 404) { + $result[] = $propertyName; + } + } + return $result; + + } + + /** + * Returns the full list of requested properties. + * + * This returns just their names, not a status or value. + * + * @return array + */ + function getRequestedProperties() { + + return $this->properties; + + } + + /** + * Returns true if this was an '{DAV:}allprops' request. + * + * @return bool + */ + function isAllProps() { + + return $this->requestType === self::ALLPROPS; + + } + + /** + * Returns a result array that's often used in multistatus responses. + * + * The array uses status codes as keys, and property names and value pairs + * as the value of the top array.. such as : + * + * [ + * 200 => [ '{DAV:}displayname' => 'foo' ], + * ] + * + * @return array + */ + function getResultForMultiStatus() { + + $r = [ + 200 => [], + 404 => [], + ]; + foreach ($this->result as $propertyName => $info) { + if (!isset($r[$info[0]])) { + $r[$info[0]] = [$propertyName => $info[1]]; + } else { + $r[$info[0]][$propertyName] = $info[1]; + } + } + // Removing the 404's for multi-status requests. + if ($this->requestType === self::ALLPROPS) unset($r[404]); + return $r; + + } + + /** + * The path that we're fetching properties for. + * + * @var string + */ + protected $path; + + /** + * The Depth of the request. + * + * 0 means only the current item. 1 means the current item + its children. + * It can also be DEPTH_INFINITY if this is enabled in the server. + * + * @var int + */ + protected $depth = 0; + + /** + * The type of request. See the TYPE constants + */ + protected $requestType; + + /** + * A list of requested properties + * + * @var array + */ + protected $properties = []; + + /** + * The result of the operation. + * + * The keys in this array are property names. + * The values are an array with two elements: the http status code and then + * optionally a value. + * + * Example: + * + * [ + * "{DAV:}owner" : [404], + * "{DAV:}displayname" : [200, "Admin"] + * ] + * + * @var array + */ + protected $result = []; + + /** + * This is used as an internal counter for the number of properties that do + * not yet have a value. + * + * @var int + */ + protected $itemsLeft; + +} diff --git a/vendor/sabre/dav/lib/DAV/PropPatch.php b/vendor/sabre/dav/lib/DAV/PropPatch.php new file mode 100644 index 000000000..6d599dacc --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/PropPatch.php @@ -0,0 +1,373 @@ +mutations = $mutations; + + } + + /** + * Call this function if you wish to handle updating certain properties. + * For instance, your class may be responsible for handling updates for the + * {DAV:}displayname property. + * + * In that case, call this method with the first argument + * "{DAV:}displayname" and a second argument that's a method that does the + * actual updating. + * + * It's possible to specify more than one property as an array. + * + * The callback must return a boolean or an it. If the result is true, the + * operation was considered successful. If it's false, it's consided + * failed. + * + * If the result is an integer, we'll use that integer as the http status + * code associated with the operation. + * + * @param string|string[] $properties + * @param callable $callback + * @return void + */ + function handle($properties, callable $callback) { + + $usedProperties = []; + foreach ((array)$properties as $propertyName) { + + if (array_key_exists($propertyName, $this->mutations) && !isset($this->result[$propertyName])) { + + $usedProperties[] = $propertyName; + // HTTP Accepted + $this->result[$propertyName] = 202; + } + + } + + // Only registering if there's any unhandled properties. + if (!$usedProperties) { + return; + } + $this->propertyUpdateCallbacks[] = [ + // If the original argument to this method was a string, we need + // to also make sure that it stays that way, so the commit function + // knows how to format the arguments to the callback. + is_string($properties) ? $properties : $usedProperties, + $callback + ]; + + } + + /** + * Call this function if you wish to handle _all_ properties that haven't + * been handled by anything else yet. Note that you effectively claim with + * this that you promise to process _all_ properties that are coming in. + * + * @param callable $callback + * @return void + */ + function handleRemaining(callable $callback) { + + $properties = $this->getRemainingMutations(); + if (!$properties) { + // Nothing to do, don't register callback + return; + } + + foreach ($properties as $propertyName) { + // HTTP Accepted + $this->result[$propertyName] = 202; + + $this->propertyUpdateCallbacks[] = [ + $properties, + $callback + ]; + } + + } + + /** + * Sets the result code for one or more properties. + * + * @param string|string[] $properties + * @param int $resultCode + * @return void + */ + function setResultCode($properties, $resultCode) { + + foreach ((array)$properties as $propertyName) { + $this->result[$propertyName] = $resultCode; + } + + if ($resultCode >= 400) { + $this->failed = true; + } + + } + + /** + * Sets the result code for all properties that did not have a result yet. + * + * @param int $resultCode + * @return void + */ + function setRemainingResultCode($resultCode) { + + $this->setResultCode( + $this->getRemainingMutations(), + $resultCode + ); + + } + + /** + * Returns the list of properties that don't have a result code yet. + * + * This method returns a list of property names, but not its values. + * + * @return string[] + */ + function getRemainingMutations() { + + $remaining = []; + foreach ($this->mutations as $propertyName => $propValue) { + if (!isset($this->result[$propertyName])) { + $remaining[] = $propertyName; + } + } + + return $remaining; + + } + + /** + * Returns the list of properties that don't have a result code yet. + * + * This method returns list of properties and their values. + * + * @return array + */ + function getRemainingValues() { + + $remaining = []; + foreach ($this->mutations as $propertyName => $propValue) { + if (!isset($this->result[$propertyName])) { + $remaining[$propertyName] = $propValue; + } + } + + return $remaining; + + } + + /** + * Performs the actual update, and calls all callbacks. + * + * This method returns true or false depending on if the operation was + * successful. + * + * @return bool + */ + function commit() { + + // First we validate if every property has a handler + foreach ($this->mutations as $propertyName => $value) { + + if (!isset($this->result[$propertyName])) { + $this->failed = true; + $this->result[$propertyName] = 403; + } + + } + + foreach ($this->propertyUpdateCallbacks as $callbackInfo) { + + if ($this->failed) { + break; + } + if (is_string($callbackInfo[0])) { + $this->doCallbackSingleProp($callbackInfo[0], $callbackInfo[1]); + } else { + $this->doCallbackMultiProp($callbackInfo[0], $callbackInfo[1]); + } + + } + + /** + * If anywhere in this operation updating a property failed, we must + * update all other properties accordingly. + */ + if ($this->failed) { + + foreach ($this->result as $propertyName => $status) { + if ($status === 202) { + // Failed dependency + $this->result[$propertyName] = 424; + } + } + + } + + return !$this->failed; + + } + + /** + * Executes a property callback with the single-property syntax. + * + * @param string $propertyName + * @param callable $callback + * @return void + */ + private function doCallBackSingleProp($propertyName, callable $callback) { + + $result = $callback($this->mutations[$propertyName]); + if (is_bool($result)) { + if ($result) { + if (is_null($this->mutations[$propertyName])) { + // Delete + $result = 204; + } else { + // Update + $result = 200; + } + } else { + // Fail + $result = 403; + } + } + if (!is_int($result)) { + throw new UnexpectedValueException('A callback sent to handle() did not return an int or a bool'); + } + $this->result[$propertyName] = $result; + if ($result >= 400) { + $this->failed = true; + } + + } + + /** + * Executes a property callback with the multi-property syntax. + * + * @param array $propertyList + * @param callable $callback + * @return void + */ + private function doCallBackMultiProp(array $propertyList, callable $callback) { + + $argument = []; + foreach ($propertyList as $propertyName) { + $argument[$propertyName] = $this->mutations[$propertyName]; + } + + $result = $callback($argument); + + if (is_array($result)) { + foreach ($propertyList as $propertyName) { + if (!isset($result[$propertyName])) { + $resultCode = 500; + } else { + $resultCode = $result[$propertyName]; + } + if ($resultCode >= 400) { + $this->failed = true; + } + $this->result[$propertyName] = $resultCode; + + } + } elseif ($result === true) { + + // Success + foreach ($argument as $propertyName => $propertyValue) { + $this->result[$propertyName] = is_null($propertyValue) ? 204 : 200; + } + + } elseif ($result === false) { + // Fail :( + $this->failed = true; + foreach ($propertyList as $propertyName) { + $this->result[$propertyName] = 403; + } + } else { + throw new UnexpectedValueException('A callback sent to handle() did not return an array or a bool'); + } + + } + + /** + * Returns the result of the operation. + * + * @return array + */ + function getResult() { + + return $this->result; + + } + + /** + * Returns the full list of mutations + * + * @return array + */ + function getMutations() { + + return $this->mutations; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/BackendInterface.php b/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/BackendInterface.php new file mode 100644 index 000000000..31ecafdb2 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/BackendInterface.php @@ -0,0 +1,80 @@ +isAllProps(). + * + * @param string $path + * @param PropFind $propFind + * @return void + */ + function propFind($path, PropFind $propFind); + + /** + * Updates properties for a path + * + * This method received a PropPatch object, which contains all the + * information about the update. + * + * Usually you would want to call 'handleRemaining' on this object, to get; + * a list of all properties that need to be stored. + * + * @param string $path + * @param PropPatch $propPatch + * @return void + */ + function propPatch($path, PropPatch $propPatch); + + /** + * This method is called after a node is deleted. + * + * This allows a backend to clean up all associated properties. + * + * The delete method will get called once for the deletion of an entire + * tree. + * + * @param string $path + * @return void + */ + function delete($path); + + /** + * This method is called after a successful MOVE + * + * This should be used to migrate all properties from one path to another. + * Note that entire collections may be moved, so ensure that all properties + * for children are also moved along. + * + * @param string $source + * @param string $destination + * @return void + */ + function move($source, $destination); + +} diff --git a/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php b/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php new file mode 100644 index 000000000..910e4979d --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php @@ -0,0 +1,217 @@ +pdo = $pdo; + + } + + /** + * Fetches properties for a path. + * + * This method received a PropFind object, which contains all the + * information about the properties that need to be fetched. + * + * Ususually you would just want to call 'get404Properties' on this object, + * as this will give you the _exact_ list of properties that need to be + * fetched, and haven't yet. + * + * However, you can also support the 'allprops' property here. In that + * case, you should check for $propFind->isAllProps(). + * + * @param string $path + * @param PropFind $propFind + * @return void + */ + function propFind($path, PropFind $propFind) { + + if (!$propFind->isAllProps() && count($propFind->get404Properties()) === 0) { + return; + } + + $query = 'SELECT name, value, valuetype FROM ' . $this->tableName . ' WHERE path = ?'; + $stmt = $this->pdo->prepare($query); + $stmt->execute([$path]); + + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + switch ($row['valuetype']) { + case null : + case self::VT_STRING : + $propFind->set($row['name'], $row['value']); + break; + case self::VT_XML : + $propFind->set($row['name'], new Complex($row['value'])); + break; + case self::VT_OBJECT : + $propFind->set($row['name'], unserialize($row['value'])); + break; + } + } + + } + + /** + * Updates properties for a path + * + * This method received a PropPatch object, which contains all the + * information about the update. + * + * Usually you would want to call 'handleRemaining' on this object, to get; + * a list of all properties that need to be stored. + * + * @param string $path + * @param PropPatch $propPatch + * @return void + */ + function propPatch($path, PropPatch $propPatch) { + + $propPatch->handleRemaining(function($properties) use ($path) { + + $updateStmt = $this->pdo->prepare("REPLACE INTO " . $this->tableName . " (path, name, valuetype, value) VALUES (?, ?, ?, ?)"); + $deleteStmt = $this->pdo->prepare("DELETE FROM " . $this->tableName . " WHERE path = ? AND name = ?"); + + foreach ($properties as $name => $value) { + + if (!is_null($value)) { + if (is_scalar($value)) { + $valueType = self::VT_STRING; + } elseif ($value instanceof Complex) { + $valueType = self::VT_XML; + $value = $value->getXml(); + } else { + $valueType = self::VT_OBJECT; + $value = serialize($value); + } + $updateStmt->execute([$path, $name, $valueType, $value]); + } else { + $deleteStmt->execute([$path, $name]); + } + + } + + return true; + + }); + + } + + /** + * This method is called after a node is deleted. + * + * This allows a backend to clean up all associated properties. + * + * The delete method will get called once for the deletion of an entire + * tree. + * + * @param string $path + * @return void + */ + function delete($path) { + + $stmt = $this->pdo->prepare("DELETE FROM " . $this->tableName . " WHERE path = ? OR path LIKE ? ESCAPE '='"); + $childPath = strtr( + $path, + [ + '=' => '==', + '%' => '=%', + '_' => '=_' + ] + ) . '/%'; + + $stmt->execute([$path, $childPath]); + + } + + /** + * This method is called after a successful MOVE + * + * This should be used to migrate all properties from one path to another. + * Note that entire collections may be moved, so ensure that all properties + * for children are also moved along. + * + * @param string $source + * @param string $destination + * @return void + */ + function move($source, $destination) { + + // I don't know a way to write this all in a single sql query that's + // also compatible across db engines, so we're letting PHP do all the + // updates. Much slower, but it should still be pretty fast in most + // cases. + $select = $this->pdo->prepare('SELECT id, path FROM ' . $this->tableName . ' WHERE path = ? OR path LIKE ?'); + $select->execute([$source, $source . '/%']); + + $update = $this->pdo->prepare('UPDATE ' . $this->tableName . ' SET path = ? WHERE id = ?'); + while ($row = $select->fetch(\PDO::FETCH_ASSOC)) { + + // Sanity check. SQL may select too many records, such as records + // with different cases. + if ($row['path'] !== $source && strpos($row['path'], $source . '/') !== 0) continue; + + $trailingPart = substr($row['path'], strlen($source) + 1); + $newPath = $destination; + if ($trailingPart) { + $newPath .= '/' . $trailingPart; + } + $update->execute([$newPath, $row['id']]); + + } + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/PropertyStorage/Plugin.php b/vendor/sabre/dav/lib/DAV/PropertyStorage/Plugin.php new file mode 100644 index 000000000..0c28b7882 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/PropertyStorage/Plugin.php @@ -0,0 +1,180 @@ +backend = $backend; + + } + + /** + * This initializes the plugin. + * + * This function is called by Sabre\DAV\Server, after + * addPlugin is called. + * + * This method should set up the required event subscriptions. + * + * @param Server $server + * @return void + */ + function initialize(Server $server) { + + $server->on('propFind', [$this, 'propFind'], 130); + $server->on('propPatch', [$this, 'propPatch'], 300); + $server->on('afterMove', [$this, 'afterMove']); + $server->on('afterUnbind', [$this, 'afterUnbind']); + + } + + /** + * Called during PROPFIND operations. + * + * If there's any requested properties that don't have a value yet, this + * plugin will look in the property storage backend to find them. + * + * @param PropFind $propFind + * @param INode $node + * @return void + */ + function propFind(PropFind $propFind, INode $node) { + + $path = $propFind->getPath(); + $pathFilter = $this->pathFilter; + if ($pathFilter && !$pathFilter($path)) return; + $this->backend->propFind($propFind->getPath(), $propFind); + + } + + /** + * Called during PROPPATCH operations + * + * If there's any updated properties that haven't been stored, the + * propertystorage backend can handle it. + * + * @param string $path + * @param PropPatch $propPatch + * @return void + */ + function propPatch($path, PropPatch $propPatch) { + + $pathFilter = $this->pathFilter; + if ($pathFilter && !$pathFilter($path)) return; + $this->backend->propPatch($path, $propPatch); + + } + + /** + * Called after a node is deleted. + * + * This allows the backend to clean up any properties still in the + * database. + * + * @param string $path + * @return void + */ + function afterUnbind($path) { + + $pathFilter = $this->pathFilter; + if ($pathFilter && !$pathFilter($path)) return; + $this->backend->delete($path); + + } + + /** + * Called after a node is moved. + * + * This allows the backend to move all the associated properties. + * + * @param string $source + * @param string $destination + * @return void + */ + function afterMove($source, $destination) { + + $pathFilter = $this->pathFilter; + if ($pathFilter && !$pathFilter($source)) return; + // If the destination is filtered, afterUnbind will handle cleaning up + // the properties. + if ($pathFilter && !$pathFilter($destination)) return; + + $this->backend->move($source, $destination); + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using \Sabre\DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'property-storage'; + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'This plugin allows any arbitrary WebDAV property to be set on any resource.', + 'link' => 'http://sabre.io/dav/property-storage/', + ]; + + } +} diff --git a/vendor/sabre/dav/lib/DAV/Server.php b/vendor/sabre/dav/lib/DAV/Server.php new file mode 100644 index 000000000..b37652812 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Server.php @@ -0,0 +1,1627 @@ + '{DAV:}collection', + ]; + + /** + * This property allows the usage of Depth: infinity on PROPFIND requests. + * + * By default Depth: infinity is treated as Depth: 1. Allowing Depth: + * infinity is potentially risky, as it allows a single client to do a full + * index of the webdav server, which is an easy DoS attack vector. + * + * Only turn this on if you know what you're doing. + * + * @var bool + */ + public $enablePropfindDepthInfinity = false; + + /** + * Reference to the XML utility object. + * + * @var Xml\Service + */ + public $xml; + + /** + * If this setting is turned off, SabreDAV's version number will be hidden + * from various places. + * + * Some people feel this is a good security measure. + * + * @var bool + */ + static $exposeVersion = true; + + /** + * Sets up the server + * + * If a Sabre\DAV\Tree object is passed as an argument, it will + * use it as the directory tree. If a Sabre\DAV\INode is passed, it + * will create a Sabre\DAV\Tree and use the node as the root. + * + * If nothing is passed, a Sabre\DAV\SimpleCollection is created in + * a Sabre\DAV\Tree. + * + * If an array is passed, we automatically create a root node, and use + * the nodes in the array as top-level children. + * + * @param Tree|INode|array|null $treeOrNode The tree object + */ + function __construct($treeOrNode = null) { + + if ($treeOrNode instanceof Tree) { + $this->tree = $treeOrNode; + } elseif ($treeOrNode instanceof INode) { + $this->tree = new Tree($treeOrNode); + } elseif (is_array($treeOrNode)) { + + // If it's an array, a list of nodes was passed, and we need to + // create the root node. + foreach ($treeOrNode as $node) { + if (!($node instanceof INode)) { + throw new Exception('Invalid argument passed to constructor. If you\'re passing an array, all the values must implement Sabre\\DAV\\INode'); + } + } + + $root = new SimpleCollection('root', $treeOrNode); + $this->tree = new Tree($root); + + } elseif (is_null($treeOrNode)) { + $root = new SimpleCollection('root'); + $this->tree = new Tree($root); + } else { + throw new Exception('Invalid argument passed to constructor. Argument must either be an instance of Sabre\\DAV\\Tree, Sabre\\DAV\\INode, an array or null'); + } + + $this->xml = new Xml\Service(); + $this->sapi = new HTTP\Sapi(); + $this->httpResponse = new HTTP\Response(); + $this->httpRequest = $this->sapi->getRequest(); + $this->addPlugin(new CorePlugin()); + + } + + /** + * Starts the DAV Server + * + * @return void + */ + function exec() { + + try { + + // If nginx (pre-1.2) is used as a proxy server, and SabreDAV as an + // origin, we must make sure we send back HTTP/1.0 if this was + // requested. + // This is mainly because nginx doesn't support Chunked Transfer + // Encoding, and this forces the webserver SabreDAV is running on, + // to buffer entire responses to calculate Content-Length. + $this->httpResponse->setHTTPVersion($this->httpRequest->getHTTPVersion()); + + // Setting the base url + $this->httpRequest->setBaseUrl($this->getBaseUri()); + $this->invokeMethod($this->httpRequest, $this->httpResponse); + + } catch (\Exception $e) { + + try { + $this->emit('exception', [$e]); + } catch (\Exception $ignore) { + } + $DOM = new \DOMDocument('1.0', 'utf-8'); + $DOM->formatOutput = true; + + $error = $DOM->createElementNS('DAV:', 'd:error'); + $error->setAttribute('xmlns:s', self::NS_SABREDAV); + $DOM->appendChild($error); + + $h = function($v) { + + return htmlspecialchars($v, ENT_NOQUOTES, 'UTF-8'); + + }; + + if (self::$exposeVersion) { + $error->appendChild($DOM->createElement('s:sabredav-version', $h(Version::VERSION))); + } + + $error->appendChild($DOM->createElement('s:exception', $h(get_class($e)))); + $error->appendChild($DOM->createElement('s:message', $h($e->getMessage()))); + if ($this->debugExceptions) { + $error->appendChild($DOM->createElement('s:file', $h($e->getFile()))); + $error->appendChild($DOM->createElement('s:line', $h($e->getLine()))); + $error->appendChild($DOM->createElement('s:code', $h($e->getCode()))); + $error->appendChild($DOM->createElement('s:stacktrace', $h($e->getTraceAsString()))); + } + + if ($this->debugExceptions) { + $previous = $e; + while ($previous = $previous->getPrevious()) { + $xPrevious = $DOM->createElement('s:previous-exception'); + $xPrevious->appendChild($DOM->createElement('s:exception', $h(get_class($previous)))); + $xPrevious->appendChild($DOM->createElement('s:message', $h($previous->getMessage()))); + $xPrevious->appendChild($DOM->createElement('s:file', $h($previous->getFile()))); + $xPrevious->appendChild($DOM->createElement('s:line', $h($previous->getLine()))); + $xPrevious->appendChild($DOM->createElement('s:code', $h($previous->getCode()))); + $xPrevious->appendChild($DOM->createElement('s:stacktrace', $h($previous->getTraceAsString()))); + $error->appendChild($xPrevious); + } + } + + + if ($e instanceof Exception) { + + $httpCode = $e->getHTTPCode(); + $e->serialize($this, $error); + $headers = $e->getHTTPHeaders($this); + + } else { + + $httpCode = 500; + $headers = []; + + } + $headers['Content-Type'] = 'application/xml; charset=utf-8'; + + $this->httpResponse->setStatus($httpCode); + $this->httpResponse->setHeaders($headers); + $this->httpResponse->setBody($DOM->saveXML()); + $this->sapi->sendResponse($this->httpResponse); + + } + + } + + /** + * Sets the base server uri + * + * @param string $uri + * @return void + */ + function setBaseUri($uri) { + + // If the baseUri does not end with a slash, we must add it + if ($uri[strlen($uri) - 1] !== '/') + $uri .= '/'; + + $this->baseUri = $uri; + + } + + /** + * Returns the base responding uri + * + * @return string + */ + function getBaseUri() { + + if (is_null($this->baseUri)) $this->baseUri = $this->guessBaseUri(); + return $this->baseUri; + + } + + /** + * This method attempts to detect the base uri. + * Only the PATH_INFO variable is considered. + * + * If this variable is not set, the root (/) is assumed. + * + * @return string + */ + function guessBaseUri() { + + $pathInfo = $this->httpRequest->getRawServerValue('PATH_INFO'); + $uri = $this->httpRequest->getRawServerValue('REQUEST_URI'); + + // If PATH_INFO is found, we can assume it's accurate. + if (!empty($pathInfo)) { + + // We need to make sure we ignore the QUERY_STRING part + if ($pos = strpos($uri, '?')) + $uri = substr($uri, 0, $pos); + + // PATH_INFO is only set for urls, such as: /example.php/path + // in that case PATH_INFO contains '/path'. + // Note that REQUEST_URI is percent encoded, while PATH_INFO is + // not, Therefore they are only comparable if we first decode + // REQUEST_INFO as well. + $decodedUri = URLUtil::decodePath($uri); + + // A simple sanity check: + if (substr($decodedUri, strlen($decodedUri) - strlen($pathInfo)) === $pathInfo) { + $baseUri = substr($decodedUri, 0, strlen($decodedUri) - strlen($pathInfo)); + return rtrim($baseUri, '/') . '/'; + } + + throw new Exception('The REQUEST_URI (' . $uri . ') did not end with the contents of PATH_INFO (' . $pathInfo . '). This server might be misconfigured.'); + + } + + // The last fallback is that we're just going to assume the server root. + return '/'; + + } + + /** + * Adds a plugin to the server + * + * For more information, console the documentation of Sabre\DAV\ServerPlugin + * + * @param ServerPlugin $plugin + * @return void + */ + function addPlugin(ServerPlugin $plugin) { + + $this->plugins[$plugin->getPluginName()] = $plugin; + $plugin->initialize($this); + + } + + /** + * Returns an initialized plugin by it's name. + * + * This function returns null if the plugin was not found. + * + * @param string $name + * @return ServerPlugin + */ + function getPlugin($name) { + + if (isset($this->plugins[$name])) + return $this->plugins[$name]; + + return null; + + } + + /** + * Returns all plugins + * + * @return array + */ + function getPlugins() { + + return $this->plugins; + + } + + /** + * Handles a http request, and execute a method based on its name + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param $sendResponse Whether to send the HTTP response to the DAV client. + * @return void + */ + function invokeMethod(RequestInterface $request, ResponseInterface $response, $sendResponse = true) { + + $method = $request->getMethod(); + + if (!$this->emit('beforeMethod:' . $method, [$request, $response])) return; + if (!$this->emit('beforeMethod', [$request, $response])) return; + + if (self::$exposeVersion) { + $response->setHeader('X-Sabre-Version', Version::VERSION); + } + + $this->transactionType = strtolower($method); + + if (!$this->checkPreconditions($request, $response)) { + $this->sapi->sendResponse($response); + return; + } + + if ($this->emit('method:' . $method, [$request, $response])) { + if ($this->emit('method', [$request, $response])) { + $exMessage = "There was no plugin in the system that was willing to handle this " . $method . " method."; + if ($method === "GET") { + $exMessage .= " Enable the Browser plugin to get a better result here."; + } + + // Unsupported method + throw new Exception\NotImplemented($exMessage); + } + } + + if (!$this->emit('afterMethod:' . $method, [$request, $response])) return; + if (!$this->emit('afterMethod', [$request, $response])) return; + + if ($response->getStatus() === null) { + throw new Exception('No subsystem set a valid HTTP status code. Something must have interrupted the request without providing further detail.'); + } + if ($sendResponse) { + $this->sapi->sendResponse($response); + $this->emit('afterResponse', [$request, $response]); + } + + } + + // {{{ HTTP/WebDAV protocol helpers + + /** + * Returns an array with all the supported HTTP methods for a specific uri. + * + * @param string $path + * @return array + */ + function getAllowedMethods($path) { + + $methods = [ + 'OPTIONS', + 'GET', + 'HEAD', + 'DELETE', + 'PROPFIND', + 'PUT', + 'PROPPATCH', + 'COPY', + 'MOVE', + 'REPORT' + ]; + + // The MKCOL is only allowed on an unmapped uri + try { + $this->tree->getNodeForPath($path); + } catch (Exception\NotFound $e) { + $methods[] = 'MKCOL'; + } + + // We're also checking if any of the plugins register any new methods + foreach ($this->plugins as $plugin) $methods = array_merge($methods, $plugin->getHTTPMethods($path)); + array_unique($methods); + + return $methods; + + } + + /** + * Gets the uri for the request, keeping the base uri into consideration + * + * @return string + */ + function getRequestUri() { + + return $this->calculateUri($this->httpRequest->getUrl()); + + } + + /** + * Turns a URI such as the REQUEST_URI into a local path. + * + * This method: + * * strips off the base path + * * normalizes the path + * * uri-decodes the path + * + * @param string $uri + * @throws Exception\Forbidden A permission denied exception is thrown whenever there was an attempt to supply a uri outside of the base uri + * @return string + */ + function calculateUri($uri) { + + if ($uri[0] != '/' && strpos($uri, '://')) { + + $uri = parse_url($uri, PHP_URL_PATH); + + } + + $uri = Uri\normalize(str_replace('//', '/', $uri)); + $baseUri = Uri\normalize($this->getBaseUri()); + + if (strpos($uri, $baseUri) === 0) { + + return trim(URLUtil::decodePath(substr($uri, strlen($baseUri))), '/'); + + // A special case, if the baseUri was accessed without a trailing + // slash, we'll accept it as well. + } elseif ($uri . '/' === $baseUri) { + + return ''; + + } else { + + throw new Exception\Forbidden('Requested uri (' . $uri . ') is out of base uri (' . $this->getBaseUri() . ')'); + + } + + } + + /** + * Returns the HTTP depth header + * + * This method returns the contents of the HTTP depth request header. If the depth header was 'infinity' it will return the Sabre\DAV\Server::DEPTH_INFINITY object + * It is possible to supply a default depth value, which is used when the depth header has invalid content, or is completely non-existent + * + * @param mixed $default + * @return int + */ + function getHTTPDepth($default = self::DEPTH_INFINITY) { + + // If its not set, we'll grab the default + $depth = $this->httpRequest->getHeader('Depth'); + + if (is_null($depth)) return $default; + + if ($depth == 'infinity') return self::DEPTH_INFINITY; + + + // If its an unknown value. we'll grab the default + if (!ctype_digit($depth)) return $default; + + return (int)$depth; + + } + + /** + * Returns the HTTP range header + * + * This method returns null if there is no well-formed HTTP range request + * header or array($start, $end). + * + * The first number is the offset of the first byte in the range. + * The second number is the offset of the last byte in the range. + * + * If the second offset is null, it should be treated as the offset of the last byte of the entity + * If the first offset is null, the second offset should be used to retrieve the last x bytes of the entity + * + * @return array|null + */ + function getHTTPRange() { + + $range = $this->httpRequest->getHeader('range'); + if (is_null($range)) return null; + + // Matching "Range: bytes=1234-5678: both numbers are optional + + if (!preg_match('/^bytes=([0-9]*)-([0-9]*)$/i', $range, $matches)) return null; + + if ($matches[1] === '' && $matches[2] === '') return null; + + return [ + $matches[1] !== '' ? $matches[1] : null, + $matches[2] !== '' ? $matches[2] : null, + ]; + + } + + /** + * Returns the HTTP Prefer header information. + * + * The prefer header is defined in: + * http://tools.ietf.org/html/draft-snell-http-prefer-14 + * + * This method will return an array with options. + * + * Currently, the following options may be returned: + * [ + * 'return-asynch' => true, + * 'return-minimal' => true, + * 'return-representation' => true, + * 'wait' => 30, + * 'strict' => true, + * 'lenient' => true, + * ] + * + * This method also supports the Brief header, and will also return + * 'return-minimal' if the brief header was set to 't'. + * + * For the boolean options, false will be returned if the headers are not + * specified. For the integer options it will be 'null'. + * + * @return array + */ + function getHTTPPrefer() { + + $result = [ + // can be true or false + 'respond-async' => false, + // Could be set to 'representation' or 'minimal'. + 'return' => null, + // Used as a timeout, is usually a number. + 'wait' => null, + // can be 'strict' or 'lenient'. + 'handling' => false, + ]; + + if ($prefer = $this->httpRequest->getHeader('Prefer')) { + + $result = array_merge( + $result, + \Sabre\HTTP\parsePrefer($prefer) + ); + + } elseif ($this->httpRequest->getHeader('Brief') == 't') { + $result['return'] = 'minimal'; + } + + return $result; + + } + + + /** + * Returns information about Copy and Move requests + * + * This function is created to help getting information about the source and the destination for the + * WebDAV MOVE and COPY HTTP request. It also validates a lot of information and throws proper exceptions + * + * The returned value is an array with the following keys: + * * destination - Destination path + * * destinationExists - Whether or not the destination is an existing url (and should therefore be overwritten) + * + * @param RequestInterface $request + * @throws Exception\BadRequest upon missing or broken request headers + * @throws Exception\UnsupportedMediaType when trying to copy into a + * non-collection. + * @throws Exception\PreconditionFailed If overwrite is set to false, but + * the destination exists. + * @throws Exception\Forbidden when source and destination paths are + * identical. + * @throws Exception\Conflict When trying to copy a node into its own + * subtree. + * @return array + */ + function getCopyAndMoveInfo(RequestInterface $request) { + + // Collecting the relevant HTTP headers + if (!$request->getHeader('Destination')) throw new Exception\BadRequest('The destination header was not supplied'); + $destination = $this->calculateUri($request->getHeader('Destination')); + $overwrite = $request->getHeader('Overwrite'); + if (!$overwrite) $overwrite = 'T'; + if (strtoupper($overwrite) == 'T') $overwrite = true; + elseif (strtoupper($overwrite) == 'F') $overwrite = false; + // We need to throw a bad request exception, if the header was invalid + else throw new Exception\BadRequest('The HTTP Overwrite header should be either T or F'); + + list($destinationDir) = URLUtil::splitPath($destination); + + try { + $destinationParent = $this->tree->getNodeForPath($destinationDir); + if (!($destinationParent instanceof ICollection)) throw new Exception\UnsupportedMediaType('The destination node is not a collection'); + } catch (Exception\NotFound $e) { + + // If the destination parent node is not found, we throw a 409 + throw new Exception\Conflict('The destination node is not found'); + } + + try { + + $destinationNode = $this->tree->getNodeForPath($destination); + + // If this succeeded, it means the destination already exists + // we'll need to throw precondition failed in case overwrite is false + if (!$overwrite) throw new Exception\PreconditionFailed('The destination node already exists, and the overwrite header is set to false', 'Overwrite'); + + } catch (Exception\NotFound $e) { + + // Destination didn't exist, we're all good + $destinationNode = false; + + } + + $requestPath = $request->getPath(); + if ($destination === $requestPath) { + throw new Exception\Forbidden('Source and destination uri are identical.'); + } + if (substr($destination, 0, strlen($requestPath) + 1) === $requestPath . '/') { + throw new Exception\Conflict('The destination may not be part of the same subtree as the source path.'); + } + + // These are the three relevant properties we need to return + return [ + 'destination' => $destination, + 'destinationExists' => !!$destinationNode, + 'destinationNode' => $destinationNode, + ]; + + } + + /** + * Returns a list of properties for a path + * + * This is a simplified version getPropertiesForPath. If you aren't + * interested in status codes, but you just want to have a flat list of + * properties, use this method. + * + * Please note though that any problems related to retrieving properties, + * such as permission issues will just result in an empty array being + * returned. + * + * @param string $path + * @param array $propertyNames + */ + function getProperties($path, $propertyNames) { + + $result = $this->getPropertiesForPath($path, $propertyNames, 0); + if (isset($result[0][200])) { + return $result[0][200]; + } else { + return []; + } + + } + + /** + * A kid-friendly way to fetch properties for a node's children. + * + * The returned array will be indexed by the path of the of child node. + * Only properties that are actually found will be returned. + * + * The parent node will not be returned. + * + * @param string $path + * @param array $propertyNames + * @return array + */ + function getPropertiesForChildren($path, $propertyNames) { + + $result = []; + foreach ($this->getPropertiesForPath($path, $propertyNames, 1) as $k => $row) { + + // Skipping the parent path + if ($k === 0) continue; + + $result[$row['href']] = $row[200]; + + } + return $result; + + } + + /** + * Returns a list of HTTP headers for a particular resource + * + * The generated http headers are based on properties provided by the + * resource. The method basically provides a simple mapping between + * DAV property and HTTP header. + * + * The headers are intended to be used for HEAD and GET requests. + * + * @param string $path + * @return array + */ + function getHTTPHeaders($path) { + + $propertyMap = [ + '{DAV:}getcontenttype' => 'Content-Type', + '{DAV:}getcontentlength' => 'Content-Length', + '{DAV:}getlastmodified' => 'Last-Modified', + '{DAV:}getetag' => 'ETag', + ]; + + $properties = $this->getProperties($path, array_keys($propertyMap)); + + $headers = []; + foreach ($propertyMap as $property => $header) { + if (!isset($properties[$property])) continue; + + if (is_scalar($properties[$property])) { + $headers[$header] = $properties[$property]; + + // GetLastModified gets special cased + } elseif ($properties[$property] instanceof Xml\Property\GetLastModified) { + $headers[$header] = HTTP\Util::toHTTPDate($properties[$property]->getTime()); + } + + } + + return $headers; + + } + + /** + * Small helper to support PROPFIND with DEPTH_INFINITY. + * + * @param array[] $propFindRequests + * @param PropFind $propFind + * @return void + */ + private function addPathNodesRecursively(&$propFindRequests, PropFind $propFind) { + + $newDepth = $propFind->getDepth(); + $path = $propFind->getPath(); + + if ($newDepth !== self::DEPTH_INFINITY) { + $newDepth--; + } + + foreach ($this->tree->getChildren($path) as $childNode) { + $subPropFind = clone $propFind; + $subPropFind->setDepth($newDepth); + if ($path !== '') { + $subPath = $path . '/' . $childNode->getName(); + } else { + $subPath = $childNode->getName(); + } + $subPropFind->setPath($subPath); + + $propFindRequests[] = [ + $subPropFind, + $childNode + ]; + + if (($newDepth === self::DEPTH_INFINITY || $newDepth >= 1) && $childNode instanceof ICollection) { + $this->addPathNodesRecursively($propFindRequests, $subPropFind); + } + + } + } + + /** + * Returns a list of properties for a given path + * + * The path that should be supplied should have the baseUrl stripped out + * The list of properties should be supplied in Clark notation. If the list is empty + * 'allprops' is assumed. + * + * If a depth of 1 is requested child elements will also be returned. + * + * @param string $path + * @param array $propertyNames + * @param int $depth + * @return array + */ + function getPropertiesForPath($path, $propertyNames = [], $depth = 0) { + + // The only two options for the depth of a propfind is 0 or 1 - as long as depth infinity is not enabled + if (!$this->enablePropfindDepthInfinity && $depth != 0) $depth = 1; + + $path = trim($path, '/'); + + $propFindType = $propertyNames ? PropFind::NORMAL : PropFind::ALLPROPS; + $propFind = new PropFind($path, (array)$propertyNames, $depth, $propFindType); + + $parentNode = $this->tree->getNodeForPath($path); + + $propFindRequests = [[ + $propFind, + $parentNode + ]]; + + if (($depth > 0 || $depth === self::DEPTH_INFINITY) && $parentNode instanceof ICollection) { + $this->addPathNodesRecursively($propFindRequests, $propFind); + } + + $returnPropertyList = []; + + foreach ($propFindRequests as $propFindRequest) { + + list($propFind, $node) = $propFindRequest; + $r = $this->getPropertiesByNode($propFind, $node); + if ($r) { + $result = $propFind->getResultForMultiStatus(); + $result['href'] = $propFind->getPath(); + + // WebDAV recommends adding a slash to the path, if the path is + // a collection. + // Furthermore, iCal also demands this to be the case for + // principals. This is non-standard, but we support it. + $resourceType = $this->getResourceTypeForNode($node); + if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) { + $result['href'] .= '/'; + } + $returnPropertyList[] = $result; + } + + } + + return $returnPropertyList; + + } + + /** + * Returns a list of properties for a list of paths. + * + * The path that should be supplied should have the baseUrl stripped out + * The list of properties should be supplied in Clark notation. If the list is empty + * 'allprops' is assumed. + * + * The result is returned as an array, with paths for it's keys. + * The result may be returned out of order. + * + * @param array $paths + * @param array $propertyNames + * @return array + */ + function getPropertiesForMultiplePaths(array $paths, array $propertyNames = []) { + + $result = [ + ]; + + $nodes = $this->tree->getMultipleNodes($paths); + + foreach ($nodes as $path => $node) { + + $propFind = new PropFind($path, $propertyNames); + $r = $this->getPropertiesByNode($propFind, $node); + if ($r) { + $result[$path] = $propFind->getResultForMultiStatus(); + $result[$path]['href'] = $path; + + $resourceType = $this->getResourceTypeForNode($node); + if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) { + $result[$path]['href'] .= '/'; + } + } + + } + + return $result; + + } + + + /** + * Determines all properties for a node. + * + * This method tries to grab all properties for a node. This method is used + * internally getPropertiesForPath and a few others. + * + * It could be useful to call this, if you already have an instance of your + * target node and simply want to run through the system to get a correct + * list of properties. + * + * @param PropFind $propFind + * @param INode $node + * @return bool + */ + function getPropertiesByNode(PropFind $propFind, INode $node) { + + return $this->emit('propFind', [$propFind, $node]); + + } + + /** + * This method is invoked by sub-systems creating a new file. + * + * Currently this is done by HTTP PUT and HTTP LOCK (in the Locks_Plugin). + * It was important to get this done through a centralized function, + * allowing plugins to intercept this using the beforeCreateFile event. + * + * This method will return true if the file was actually created + * + * @param string $uri + * @param resource $data + * @param string $etag + * @return bool + */ + function createFile($uri, $data, &$etag = null) { + + list($dir, $name) = URLUtil::splitPath($uri); + + if (!$this->emit('beforeBind', [$uri])) return false; + + $parent = $this->tree->getNodeForPath($dir); + if (!$parent instanceof ICollection) { + throw new Exception\Conflict('Files can only be created as children of collections'); + } + + // It is possible for an event handler to modify the content of the + // body, before it gets written. If this is the case, $modified + // should be set to true. + // + // If $modified is true, we must not send back an ETag. + $modified = false; + if (!$this->emit('beforeCreateFile', [$uri, &$data, $parent, &$modified])) return false; + + $etag = $parent->createFile($name, $data); + + if ($modified) $etag = null; + + $this->tree->markDirty($dir . '/' . $name); + + $this->emit('afterBind', [$uri]); + $this->emit('afterCreateFile', [$uri, $parent]); + + return true; + } + + /** + * This method is invoked by sub-systems updating a file. + * + * This method will return true if the file was actually updated + * + * @param string $uri + * @param resource $data + * @param string $etag + * @return bool + */ + function updateFile($uri, $data, &$etag = null) { + + $node = $this->tree->getNodeForPath($uri); + + // It is possible for an event handler to modify the content of the + // body, before it gets written. If this is the case, $modified + // should be set to true. + // + // If $modified is true, we must not send back an ETag. + $modified = false; + if (!$this->emit('beforeWriteContent', [$uri, $node, &$data, &$modified])) return false; + + $etag = $node->put($data); + if ($modified) $etag = null; + $this->emit('afterWriteContent', [$uri, $node]); + + return true; + } + + + + /** + * This method is invoked by sub-systems creating a new directory. + * + * @param string $uri + * @return void + */ + function createDirectory($uri) { + + $this->createCollection($uri, new MkCol(['{DAV:}collection'], [])); + + } + + /** + * Use this method to create a new collection + * + * @param string $uri The new uri + * @param MkCol $mkCol + * @return array|null + */ + function createCollection($uri, MkCol $mkCol) { + + list($parentUri, $newName) = URLUtil::splitPath($uri); + + // Making sure the parent exists + try { + $parent = $this->tree->getNodeForPath($parentUri); + + } catch (Exception\NotFound $e) { + throw new Exception\Conflict('Parent node does not exist'); + + } + + // Making sure the parent is a collection + if (!$parent instanceof ICollection) { + throw new Exception\Conflict('Parent node is not a collection'); + } + + // Making sure the child does not already exist + try { + $parent->getChild($newName); + + // If we got here.. it means there's already a node on that url, and we need to throw a 405 + throw new Exception\MethodNotAllowed('The resource you tried to create already exists'); + + } catch (Exception\NotFound $e) { + // NotFound is the expected behavior. + } + + + if (!$this->emit('beforeBind', [$uri])) return; + + if ($parent instanceof IExtendedCollection) { + + /** + * If the parent is an instance of IExtendedCollection, it means that + * we can pass the MkCol object directly as it may be able to store + * properties immediately. + */ + $parent->createExtendedCollection($newName, $mkCol); + + } else { + + /** + * If the parent is a standard ICollection, it means only + * 'standard' collections can be created, so we should fail any + * MKCOL operation that carries extra resourcetypes. + */ + if (count($mkCol->getResourceType()) > 1) { + throw new Exception\InvalidResourceType('The {DAV:}resourcetype you specified is not supported here.'); + } + + $parent->createDirectory($newName); + + } + + // If there are any properties that have not been handled/stored, + // we ask the 'propPatch' event to handle them. This will allow for + // example the propertyStorage system to store properties upon MKCOL. + if ($mkCol->getRemainingMutations()) { + $this->emit('propPatch', [$uri, $mkCol]); + } + $success = $mkCol->commit(); + + if (!$success) { + $result = $mkCol->getResult(); + // generateMkCol needs the href key to exist. + $result['href'] = $uri; + return $result; + } + + $this->tree->markDirty($parentUri); + $this->emit('afterBind', [$uri]); + + } + + /** + * This method updates a resource's properties + * + * The properties array must be a list of properties. Array-keys are + * property names in clarknotation, array-values are it's values. + * If a property must be deleted, the value should be null. + * + * Note that this request should either completely succeed, or + * completely fail. + * + * The response is an array with properties for keys, and http status codes + * as their values. + * + * @param string $path + * @param array $properties + * @return array + */ + function updateProperties($path, array $properties) { + + $propPatch = new PropPatch($properties); + $this->emit('propPatch', [$path, $propPatch]); + $propPatch->commit(); + + return $propPatch->getResult(); + + } + + /** + * This method checks the main HTTP preconditions. + * + * Currently these are: + * * If-Match + * * If-None-Match + * * If-Modified-Since + * * If-Unmodified-Since + * + * The method will return true if all preconditions are met + * The method will return false, or throw an exception if preconditions + * failed. If false is returned the operation should be aborted, and + * the appropriate HTTP response headers are already set. + * + * Normally this method will throw 412 Precondition Failed for failures + * related to If-None-Match, If-Match and If-Unmodified Since. It will + * set the status to 304 Not Modified for If-Modified_since. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function checkPreconditions(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + $node = null; + $lastMod = null; + $etag = null; + + if ($ifMatch = $request->getHeader('If-Match')) { + + // If-Match contains an entity tag. Only if the entity-tag + // matches we are allowed to make the request succeed. + // If the entity-tag is '*' we are only allowed to make the + // request succeed if a resource exists at that url. + try { + $node = $this->tree->getNodeForPath($path); + } catch (Exception\NotFound $e) { + throw new Exception\PreconditionFailed('An If-Match header was specified and the resource did not exist', 'If-Match'); + } + + // Only need to check entity tags if they are not * + if ($ifMatch !== '*') { + + // There can be multiple ETags + $ifMatch = explode(',', $ifMatch); + $haveMatch = false; + foreach ($ifMatch as $ifMatchItem) { + + // Stripping any extra spaces + $ifMatchItem = trim($ifMatchItem, ' '); + + $etag = $node instanceof IFile ? $node->getETag() : null; + if ($etag === $ifMatchItem) { + $haveMatch = true; + } else { + // Evolution has a bug where it sometimes prepends the " + // with a \. This is our workaround. + if (str_replace('\\"', '"', $ifMatchItem) === $etag) { + $haveMatch = true; + } + } + + } + if (!$haveMatch) { + if ($etag) $response->setHeader('ETag', $etag); + throw new Exception\PreconditionFailed('An If-Match header was specified, but none of the specified the ETags matched.', 'If-Match'); + } + } + } + + if ($ifNoneMatch = $request->getHeader('If-None-Match')) { + + // The If-None-Match header contains an ETag. + // Only if the ETag does not match the current ETag, the request will succeed + // The header can also contain *, in which case the request + // will only succeed if the entity does not exist at all. + $nodeExists = true; + if (!$node) { + try { + $node = $this->tree->getNodeForPath($path); + } catch (Exception\NotFound $e) { + $nodeExists = false; + } + } + if ($nodeExists) { + $haveMatch = false; + if ($ifNoneMatch === '*') $haveMatch = true; + else { + + // There might be multiple ETags + $ifNoneMatch = explode(',', $ifNoneMatch); + $etag = $node instanceof IFile ? $node->getETag() : null; + + foreach ($ifNoneMatch as $ifNoneMatchItem) { + + // Stripping any extra spaces + $ifNoneMatchItem = trim($ifNoneMatchItem, ' '); + + if ($etag === $ifNoneMatchItem) $haveMatch = true; + + } + + } + + if ($haveMatch) { + if ($etag) $response->setHeader('ETag', $etag); + if ($request->getMethod() === 'GET') { + $response->setStatus(304); + return false; + } else { + throw new Exception\PreconditionFailed('An If-None-Match header was specified, but the ETag matched (or * was specified).', 'If-None-Match'); + } + } + } + + } + + if (!$ifNoneMatch && ($ifModifiedSince = $request->getHeader('If-Modified-Since'))) { + + // The If-Modified-Since header contains a date. We + // will only return the entity if it has been changed since + // that date. If it hasn't been changed, we return a 304 + // header + // Note that this header only has to be checked if there was no If-None-Match header + // as per the HTTP spec. + $date = HTTP\Util::parseHTTPDate($ifModifiedSince); + + if ($date) { + if (is_null($node)) { + $node = $this->tree->getNodeForPath($path); + } + $lastMod = $node->getLastModified(); + if ($lastMod) { + $lastMod = new \DateTime('@' . $lastMod); + if ($lastMod <= $date) { + $response->setStatus(304); + $response->setHeader('Last-Modified', HTTP\Util::toHTTPDate($lastMod)); + return false; + } + } + } + } + + if ($ifUnmodifiedSince = $request->getHeader('If-Unmodified-Since')) { + + // The If-Unmodified-Since will allow allow the request if the + // entity has not changed since the specified date. + $date = HTTP\Util::parseHTTPDate($ifUnmodifiedSince); + + // We must only check the date if it's valid + if ($date) { + if (is_null($node)) { + $node = $this->tree->getNodeForPath($path); + } + $lastMod = $node->getLastModified(); + if ($lastMod) { + $lastMod = new \DateTime('@' . $lastMod); + if ($lastMod > $date) { + throw new Exception\PreconditionFailed('An If-Unmodified-Since header was specified, but the entity has been changed since the specified date.', 'If-Unmodified-Since'); + } + } + } + + } + + // Now the hardest, the If: header. The If: header can contain multiple + // urls, ETags and so-called 'state tokens'. + // + // Examples of state tokens include lock-tokens (as defined in rfc4918) + // and sync-tokens (as defined in rfc6578). + // + // The only proper way to deal with these, is to emit events, that a + // Sync and Lock plugin can pick up. + $ifConditions = $this->getIfConditions($request); + + foreach ($ifConditions as $kk => $ifCondition) { + foreach ($ifCondition['tokens'] as $ii => $token) { + $ifConditions[$kk]['tokens'][$ii]['validToken'] = false; + } + } + + // Plugins are responsible for validating all the tokens. + // If a plugin deemed a token 'valid', it will set 'validToken' to + // true. + $this->emit('validateTokens', [ $request, &$ifConditions ]); + + // Now we're going to analyze the result. + + // Every ifCondition needs to validate to true, so we exit as soon as + // we have an invalid condition. + foreach ($ifConditions as $ifCondition) { + + $uri = $ifCondition['uri']; + $tokens = $ifCondition['tokens']; + + // We only need 1 valid token for the condition to succeed. + foreach ($tokens as $token) { + + $tokenValid = $token['validToken'] || !$token['token']; + + $etagValid = false; + if (!$token['etag']) { + $etagValid = true; + } + // Checking the ETag, only if the token was already deamed + // valid and there is one. + if ($token['etag'] && $tokenValid) { + + // The token was valid, and there was an ETag. We must + // grab the current ETag and check it. + $node = $this->tree->getNodeForPath($uri); + $etagValid = $node instanceof IFile && $node->getETag() == $token['etag']; + + } + + + if (($tokenValid && $etagValid) ^ $token['negate']) { + // Both were valid, so we can go to the next condition. + continue 2; + } + + + } + + // If we ended here, it means there was no valid ETag + token + // combination found for the current condition. This means we fail! + throw new Exception\PreconditionFailed('Failed to find a valid token/etag combination for ' . $uri, 'If'); + + } + + return true; + + } + + /** + * This method is created to extract information from the WebDAV HTTP 'If:' header + * + * The If header can be quite complex, and has a bunch of features. We're using a regex to extract all relevant information + * The function will return an array, containing structs with the following keys + * + * * uri - the uri the condition applies to. + * * tokens - The lock token. another 2 dimensional array containing 3 elements + * + * Example 1: + * + * If: () + * + * Would result in: + * + * [ + * [ + * 'uri' => '/request/uri', + * 'tokens' => [ + * [ + * [ + * 'negate' => false, + * 'token' => 'opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2', + * 'etag' => "" + * ] + * ] + * ], + * ] + * ] + * + * Example 2: + * + * If: (Not ["Im An ETag"]) (["Another ETag"]) (Not ["Path2 ETag"]) + * + * Would result in: + * + * [ + * [ + * 'uri' => 'path', + * 'tokens' => [ + * [ + * [ + * 'negate' => true, + * 'token' => 'opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2', + * 'etag' => '"Im An ETag"' + * ], + * [ + * 'negate' => false, + * 'token' => '', + * 'etag' => '"Another ETag"' + * ] + * ] + * ], + * ], + * [ + * 'uri' => 'path2', + * 'tokens' => [ + * [ + * [ + * 'negate' => true, + * 'token' => '', + * 'etag' => '"Path2 ETag"' + * ] + * ] + * ], + * ], + * ] + * + * @param RequestInterface $request + * @return array + */ + function getIfConditions(RequestInterface $request) { + + $header = $request->getHeader('If'); + if (!$header) return []; + + $matches = []; + + $regex = '/(?:\<(?P.*?)\>\s)?\((?PNot\s)?(?:\<(?P[^\>]*)\>)?(?:\s?)(?:\[(?P[^\]]*)\])?\)/im'; + preg_match_all($regex, $header, $matches, PREG_SET_ORDER); + + $conditions = []; + + foreach ($matches as $match) { + + // If there was no uri specified in this match, and there were + // already conditions parsed, we add the condition to the list of + // conditions for the previous uri. + if (!$match['uri'] && count($conditions)) { + $conditions[count($conditions) - 1]['tokens'][] = [ + 'negate' => $match['not'] ? true : false, + 'token' => $match['token'], + 'etag' => isset($match['etag']) ? $match['etag'] : '' + ]; + } else { + + if (!$match['uri']) { + $realUri = $request->getPath(); + } else { + $realUri = $this->calculateUri($match['uri']); + } + + $conditions[] = [ + 'uri' => $realUri, + 'tokens' => [ + [ + 'negate' => $match['not'] ? true : false, + 'token' => $match['token'], + 'etag' => isset($match['etag']) ? $match['etag'] : '' + ] + ], + + ]; + } + + } + + return $conditions; + + } + + /** + * Returns an array with resourcetypes for a node. + * + * @param INode $node + * @return array + */ + function getResourceTypeForNode(INode $node) { + + $result = []; + foreach ($this->resourceTypeMapping as $className => $resourceType) { + if ($node instanceof $className) $result[] = $resourceType; + } + return $result; + + } + + // }}} + // {{{ XML Readers & Writers + + + /** + * Generates a WebDAV propfind response body based on a list of nodes. + * + * If 'strip404s' is set to true, all 404 responses will be removed. + * + * @param array $fileProperties The list with nodes + * @param bool strip404s + * @return string + */ + function generateMultiStatus(array $fileProperties, $strip404s = false) { + + $xml = []; + + foreach ($fileProperties as $entry) { + + $href = $entry['href']; + unset($entry['href']); + if ($strip404s) { + unset($entry[404]); + } + $response = new Xml\Element\Response( + ltrim($href, '/'), + $entry + ); + $xml[] = [ + 'name' => '{DAV:}response', + 'value' => $response + ]; + + } + return $this->xml->write('{DAV:}multistatus', $xml, $this->baseUri); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/ServerPlugin.php b/vendor/sabre/dav/lib/DAV/ServerPlugin.php new file mode 100644 index 000000000..b2c468ab3 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/ServerPlugin.php @@ -0,0 +1,110 @@ + $this->getPluginName(), + 'description' => null, + 'link' => null, + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/SimpleCollection.php b/vendor/sabre/dav/lib/DAV/SimpleCollection.php new file mode 100644 index 000000000..998cfcbff --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/SimpleCollection.php @@ -0,0 +1,107 @@ +name = $name; + foreach ($children as $child) { + + if (!($child instanceof INode)) throw new Exception('Only instances of Sabre\DAV\INode are allowed to be passed in the children argument'); + $this->addChild($child); + + } + + } + + /** + * Adds a new childnode to this collection + * + * @param INode $child + * @return void + */ + function addChild(INode $child) { + + $this->children[$child->getName()] = $child; + + } + + /** + * Returns the name of the collection + * + * @return string + */ + function getName() { + + return $this->name; + + } + + /** + * Returns a child object, by its name. + * + * This method makes use of the getChildren method to grab all the child nodes, and compares the name. + * Generally its wise to override this, as this can usually be optimized + * + * This method must throw Sabre\DAV\Exception\NotFound if the node does not + * exist. + * + * @param string $name + * @throws Exception\NotFound + * @return INode + */ + function getChild($name) { + + if (isset($this->children[$name])) return $this->children[$name]; + throw new Exception\NotFound('File not found: ' . $name . ' in \'' . $this->getName() . '\''); + + } + + /** + * Returns a list of children for this collection + * + * @return INode[] + */ + function getChildren() { + + return array_values($this->children); + + } + + +} diff --git a/vendor/sabre/dav/lib/DAV/SimpleFile.php b/vendor/sabre/dav/lib/DAV/SimpleFile.php new file mode 100644 index 000000000..bcad786f3 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/SimpleFile.php @@ -0,0 +1,121 @@ +name = $name; + $this->contents = $contents; + $this->mimeType = $mimeType; + + } + + /** + * Returns the node name for this file. + * + * This name is used to construct the url. + * + * @return string + */ + function getName() { + + return $this->name; + + } + + /** + * Returns the data + * + * This method may either return a string or a readable stream resource + * + * @return mixed + */ + function get() { + + return $this->contents; + + } + + /** + * Returns the size of the file, in bytes. + * + * @return int + */ + function getSize() { + + return strlen($this->contents); + + } + + /** + * Returns the ETag for a file + * + * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. + * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. + * + * Return null if the ETag can not effectively be determined + * @return string + */ + function getETag() { + + return '"' . sha1($this->contents) . '"'; + + } + + /** + * Returns the mime-type for a file + * + * If null is returned, we'll assume application/octet-stream + * @return string + */ + function getContentType() { + + return $this->mimeType; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/StringUtil.php b/vendor/sabre/dav/lib/DAV/StringUtil.php new file mode 100644 index 000000000..10eecebfd --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/StringUtil.php @@ -0,0 +1,91 @@ + 'The current synctoken', + * 'added' => [ + * 'new.txt', + * ], + * 'modified' => [ + * 'modified.txt', + * ], + * 'deleted' => array( + * 'foo.php.bak', + * 'old.txt' + * ) + * ]; + * + * The syncToken property should reflect the *current* syncToken of the + * collection, as reported getSyncToken(). This is needed here too, to + * ensure the operation is atomic. + * + * If the syncToken is specified as null, this is an initial sync, and all + * members should be reported. + * + * The modified property is an array of nodenames that have changed since + * the last token. + * + * The deleted property is an array with nodenames, that have been deleted + * from collection. + * + * The second argument is basically the 'depth' of the report. If it's 1, + * you only have to report changes that happened only directly in immediate + * descendants. If it's 2, it should also include changes from the nodes + * below the child collections. (grandchildren) + * + * The third (optional) argument allows a client to specify how many + * results should be returned at most. If the limit is not specified, it + * should be treated as infinite. + * + * If the limit (infinite or not) is higher than you're willing to return, + * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. + * + * If the syncToken is expired (due to data cleanup) or unknown, you must + * return null. + * + * The limit is 'suggestive'. You are free to ignore it. + * + * @param string $syncToken + * @param int $syncLevel + * @param int $limit + * @return array + */ + function getChanges($syncToken, $syncLevel, $limit = null); + +} diff --git a/vendor/sabre/dav/lib/DAV/Sync/Plugin.php b/vendor/sabre/dav/lib/DAV/Sync/Plugin.php new file mode 100644 index 000000000..4a141c72b --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Sync/Plugin.php @@ -0,0 +1,277 @@ +server = $server; + $server->xml->elementMap['{DAV:}sync-collection'] = 'Sabre\\DAV\\Xml\\Request\\SyncCollectionReport'; + + $self = $this; + + $server->on('report', function($reportName, $dom, $uri) use ($self) { + + if ($reportName === '{DAV:}sync-collection') { + $this->server->transactionType = 'report-sync-collection'; + $self->syncCollection($uri, $dom); + return false; + } + + }); + + $server->on('propFind', [$this, 'propFind']); + $server->on('validateTokens', [$this, 'validateTokens']); + + } + + /** + * Returns a list of reports this plugin supports. + * + * This will be used in the {DAV:}supported-report-set property. + * Note that you still need to subscribe to the 'report' event to actually + * implement them + * + * @param string $uri + * @return array + */ + function getSupportedReportSet($uri) { + + $node = $this->server->tree->getNodeForPath($uri); + if ($node instanceof ISyncCollection && $node->getSyncToken()) { + return [ + '{DAV:}sync-collection', + ]; + } + + return []; + + } + + + /** + * This method handles the {DAV:}sync-collection HTTP REPORT. + * + * @param string $uri + * @param SyncCollectionReport $report + * @return void + */ + function syncCollection($uri, SyncCollectionReport $report) { + + // Getting the data + $node = $this->server->tree->getNodeForPath($uri); + if (!$node instanceof ISyncCollection) { + throw new DAV\Exception\ReportNotSupported('The {DAV:}sync-collection REPORT is not supported on this url.'); + } + $token = $node->getSyncToken(); + if (!$token) { + throw new DAV\Exception\ReportNotSupported('No sync information is available at this node'); + } + + $syncToken = $report->syncToken; + if (!is_null($syncToken)) { + // Sync-token must start with our prefix + if (substr($syncToken, 0, strlen(self::SYNCTOKEN_PREFIX)) !== self::SYNCTOKEN_PREFIX) { + throw new DAV\Exception\InvalidSyncToken('Invalid or unknown sync token'); + } + + $syncToken = substr($syncToken, strlen(self::SYNCTOKEN_PREFIX)); + + } + $changeInfo = $node->getChanges($syncToken, $report->syncLevel, $report->limit); + + if (is_null($changeInfo)) { + + throw new DAV\Exception\InvalidSyncToken('Invalid or unknown sync token'); + + } + + // Encoding the response + $this->sendSyncCollectionResponse( + $changeInfo['syncToken'], + $uri, + $changeInfo['added'], + $changeInfo['modified'], + $changeInfo['deleted'], + $report->properties + ); + + } + + /** + * Sends the response to a sync-collection request. + * + * @param string $syncToken + * @param string $collectionUrl + * @param array $added + * @param array $modified + * @param array $deleted + * @param array $properties + * @return void + */ + protected function sendSyncCollectionResponse($syncToken, $collectionUrl, array $added, array $modified, array $deleted, array $properties) { + + + $fullPaths = []; + + // Pre-fetching children, if this is possible. + foreach (array_merge($added, $modified) as $item) { + $fullPath = $collectionUrl . '/' . $item; + $fullPaths[] = $fullPath; + } + + $responses = []; + foreach ($this->server->getPropertiesForMultiplePaths($fullPaths, $properties) as $fullPath => $props) { + + // The 'Property_Response' class is responsible for generating a + // single {DAV:}response xml element. + $responses[] = new DAV\Xml\Element\Response($fullPath, $props); + + } + + + + // Deleted items also show up as 'responses'. They have no properties, + // and a single {DAV:}status element set as 'HTTP/1.1 404 Not Found'. + foreach ($deleted as $item) { + + $fullPath = $collectionUrl . '/' . $item; + $responses[] = new DAV\Xml\Element\Response($fullPath, [], 404); + + } + $multiStatus = new DAV\Xml\Response\MultiStatus($responses, self::SYNCTOKEN_PREFIX . $syncToken); + + $this->server->httpResponse->setStatus(207); + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setBody( + $this->server->xml->write('{DAV:}multistatus', $multiStatus, $this->server->getBaseUri()) + ); + + } + + /** + * This method is triggered whenever properties are requested for a node. + * We intercept this to see if we must return a {DAV:}sync-token. + * + * @param DAV\PropFind $propFind + * @param DAV\INode $node + * @return void + */ + function propFind(DAV\PropFind $propFind, DAV\INode $node) { + + $propFind->handle('{DAV:}sync-token', function() use ($node) { + if (!$node instanceof ISyncCollection || !$token = $node->getSyncToken()) { + return; + } + return self::SYNCTOKEN_PREFIX . $token; + }); + + } + + /** + * The validateTokens event is triggered before every request. + * + * It's a moment where this plugin can check all the supplied lock tokens + * in the If: header, and check if they are valid. + * + * @param RequestInterface $request + * @param array $conditions + * @return void + */ + function validateTokens(RequestInterface $request, &$conditions) { + + foreach ($conditions as $kk => $condition) { + + foreach ($condition['tokens'] as $ii => $token) { + + // Sync-tokens must always start with our designated prefix. + if (substr($token['token'], 0, strlen(self::SYNCTOKEN_PREFIX)) !== self::SYNCTOKEN_PREFIX) { + continue; + } + + // Checking if the token is a match. + $node = $this->server->tree->getNodeForPath($condition['uri']); + + if ( + $node instanceof ISyncCollection && + $node->getSyncToken() == substr($token['token'], strlen(self::SYNCTOKEN_PREFIX)) + ) { + $conditions[$kk]['tokens'][$ii]['validToken'] = true; + } + + } + + } + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Adds support for WebDAV Collection Sync (rfc6578)', + 'link' => 'http://sabre.io/dav/sync/', + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php b/vendor/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php new file mode 100644 index 000000000..c5b8aa1ca --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php @@ -0,0 +1,297 @@ +dataDir = $dataDir; + + } + + /** + * Initialize the plugin + * + * This is called automatically be the Server class after this plugin is + * added with Sabre\DAV\Server::addPlugin() + * + * @param Server $server + * @return void + */ + function initialize(Server $server) { + + $this->server = $server; + $server->on('beforeMethod', [$this, 'beforeMethod']); + $server->on('beforeCreateFile', [$this, 'beforeCreateFile']); + + } + + /** + * This method is called before any HTTP method handler + * + * This method intercepts any GET, DELETE, PUT and PROPFIND calls to + * filenames that are known to match the 'temporary file' regex. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function beforeMethod(RequestInterface $request, ResponseInterface $response) { + + if (!$tempLocation = $this->isTempFile($request->getPath())) + return; + + switch ($request->getMethod()) { + case 'GET' : + return $this->httpGet($request, $response, $tempLocation); + case 'PUT' : + return $this->httpPut($request, $response, $tempLocation); + case 'PROPFIND' : + return $this->httpPropfind($request, $response, $tempLocation); + case 'DELETE' : + return $this->httpDelete($request, $response, $tempLocation); + } + return; + + } + + /** + * This method is invoked if some subsystem creates a new file. + * + * This is used to deal with HTTP LOCK requests which create a new + * file. + * + * @param string $uri + * @param resource $data + * @param DAV\ICollection $parentNode + * @param bool $modified Should be set to true, if this event handler + * changed &$data. + * @return bool + */ + function beforeCreateFile($uri, $data, $parent, $modified) { + + if ($tempPath = $this->isTempFile($uri)) { + + $hR = $this->server->httpResponse; + $hR->setHeader('X-Sabre-Temp', 'true'); + file_put_contents($tempPath, $data); + return false; + } + return; + + } + + /** + * This method will check if the url matches the temporary file pattern + * if it does, it will return an path based on $this->dataDir for the + * temporary file storage. + * + * @param string $path + * @return bool|string + */ + protected function isTempFile($path) { + + // We're only interested in the basename. + list(, $tempPath) = URLUtil::splitPath($path); + + foreach ($this->temporaryFilePatterns as $tempFile) { + + if (preg_match($tempFile, $tempPath)) { + return $this->getDataDir() . '/sabredav_' . md5($path) . '.tempfile'; + } + + } + + return false; + + } + + + /** + * This method handles the GET method for temporary files. + * If the file doesn't exist, it will return false which will kick in + * the regular system for the GET method. + * + * @param RequestInterface $request + * @param ResponseInterface $hR + * @param string $tempLocation + * @return bool + */ + function httpGet(RequestInterface $request, ResponseInterface $hR, $tempLocation) { + + if (!file_exists($tempLocation)) return; + + $hR->setHeader('Content-Type', 'application/octet-stream'); + $hR->setHeader('Content-Length', filesize($tempLocation)); + $hR->setHeader('X-Sabre-Temp', 'true'); + $hR->setStatus(200); + $hR->setBody(fopen($tempLocation, 'r')); + return false; + + } + + /** + * This method handles the PUT method. + * + * @param RequestInterface $request + * @param ResponseInterface $hR + * @param string $tempLocation + * @return bool + */ + function httpPut(RequestInterface $request, ResponseInterface $hR, $tempLocation) { + + $hR->setHeader('X-Sabre-Temp', 'true'); + + $newFile = !file_exists($tempLocation); + + if (!$newFile && ($this->server->httpRequest->getHeader('If-None-Match'))) { + throw new Exception\PreconditionFailed('The resource already exists, and an If-None-Match header was supplied'); + } + + file_put_contents($tempLocation, $this->server->httpRequest->getBody()); + $hR->setStatus($newFile ? 201 : 200); + return false; + + } + + /** + * This method handles the DELETE method. + * + * If the file didn't exist, it will return false, which will make the + * standard HTTP DELETE handler kick in. + * + * @param RequestInterface $request + * @param ResponseInterface $hR + * @param string $tempLocation + * @return bool + */ + function httpDelete(RequestInterface $request, ResponseInterface $hR, $tempLocation) { + + if (!file_exists($tempLocation)) return; + + unlink($tempLocation); + $hR->setHeader('X-Sabre-Temp', 'true'); + $hR->setStatus(204); + return false; + + } + + /** + * This method handles the PROPFIND method. + * + * It's a very lazy method, it won't bother checking the request body + * for which properties were requested, and just sends back a default + * set of properties. + * + * @param RequestInterface $request + * @param ResponseInterface $hR + * @param string $tempLocation + * @return bool + */ + function httpPropfind(RequestInterface $request, ResponseInterface $hR, $tempLocation) { + + if (!file_exists($tempLocation)) return; + + $hR->setHeader('X-Sabre-Temp', 'true'); + $hR->setStatus(207); + $hR->setHeader('Content-Type', 'application/xml; charset=utf-8'); + + $properties = [ + 'href' => $request->getPath(), + 200 => [ + '{DAV:}getlastmodified' => new Xml\Property\GetLastModified(filemtime($tempLocation)), + '{DAV:}getcontentlength' => filesize($tempLocation), + '{DAV:}resourcetype' => new Xml\Property\ResourceType(null), + '{' . Server::NS_SABREDAV . '}tempFile' => true, + + ], + ]; + + $data = $this->server->generateMultiStatus([$properties]); + $hR->setBody($data); + return false; + + } + + + /** + * This method returns the directory where the temporary files should be stored. + * + * @return string + */ + protected function getDataDir() + { + return $this->dataDir; + } +} diff --git a/vendor/sabre/dav/lib/DAV/Tree.php b/vendor/sabre/dav/lib/DAV/Tree.php new file mode 100644 index 000000000..4563f7c72 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Tree.php @@ -0,0 +1,340 @@ +rootNode = $rootNode; + + } + + /** + * Returns the INode object for the requested path + * + * @param string $path + * @return INode + */ + function getNodeForPath($path) { + + $path = trim($path, '/'); + if (isset($this->cache[$path])) return $this->cache[$path]; + + // Is it the root node? + if (!strlen($path)) { + return $this->rootNode; + } + + // Attempting to fetch its parent + list($parentName, $baseName) = URLUtil::splitPath($path); + + // If there was no parent, we must simply ask it from the root node. + if ($parentName === "") { + $node = $this->rootNode->getChild($baseName); + } else { + // Otherwise, we recursively grab the parent and ask him/her. + $parent = $this->getNodeForPath($parentName); + + if (!($parent instanceof ICollection)) + throw new Exception\NotFound('Could not find node at path: ' . $path); + + $node = $parent->getChild($baseName); + + } + + $this->cache[$path] = $node; + return $node; + + } + + /** + * This function allows you to check if a node exists. + * + * Implementors of this class should override this method to make + * it cheaper. + * + * @param string $path + * @return bool + */ + function nodeExists($path) { + + try { + + // The root always exists + if ($path === '') return true; + + list($parent, $base) = URLUtil::splitPath($path); + + $parentNode = $this->getNodeForPath($parent); + if (!$parentNode instanceof ICollection) return false; + return $parentNode->childExists($base); + + } catch (Exception\NotFound $e) { + + return false; + + } + + } + + /** + * Copies a file from path to another + * + * @param string $sourcePath The source location + * @param string $destinationPath The full destination path + * @return void + */ + function copy($sourcePath, $destinationPath) { + + $sourceNode = $this->getNodeForPath($sourcePath); + + // grab the dirname and basename components + list($destinationDir, $destinationName) = URLUtil::splitPath($destinationPath); + + $destinationParent = $this->getNodeForPath($destinationDir); + $this->copyNode($sourceNode, $destinationParent, $destinationName); + + $this->markDirty($destinationDir); + + } + + /** + * Moves a file from one location to another + * + * @param string $sourcePath The path to the file which should be moved + * @param string $destinationPath The full destination path, so not just the destination parent node + * @return int + */ + function move($sourcePath, $destinationPath) { + + list($sourceDir) = URLUtil::splitPath($sourcePath); + list($destinationDir, $destinationName) = URLUtil::splitPath($destinationPath); + + if ($sourceDir === $destinationDir) { + // If this is a 'local' rename, it means we can just trigger a rename. + $sourceNode = $this->getNodeForPath($sourcePath); + $sourceNode->setName($destinationName); + } else { + $newParentNode = $this->getNodeForPath($destinationDir); + $moveSuccess = false; + if ($newParentNode instanceof IMoveTarget) { + // The target collection may be able to handle the move + $sourceNode = $this->getNodeForPath($sourcePath); + $moveSuccess = $newParentNode->moveInto($destinationName, $sourcePath, $sourceNode); + } + if (!$moveSuccess) { + $this->copy($sourcePath, $destinationPath); + $this->getNodeForPath($sourcePath)->delete(); + } + } + $this->markDirty($sourceDir); + $this->markDirty($destinationDir); + + } + + /** + * Deletes a node from the tree + * + * @param string $path + * @return void + */ + function delete($path) { + + $node = $this->getNodeForPath($path); + $node->delete(); + + list($parent) = URLUtil::splitPath($path); + $this->markDirty($parent); + + } + + /** + * Returns a list of childnodes for a given path. + * + * @param string $path + * @return array + */ + function getChildren($path) { + + $node = $this->getNodeForPath($path); + $children = $node->getChildren(); + $basePath = trim($path, '/'); + if ($basePath !== '') $basePath .= '/'; + + foreach ($children as $child) { + + $this->cache[$basePath . $child->getName()] = $child; + + } + return $children; + + } + + /** + * This method is called with every tree update + * + * Examples of tree updates are: + * * node deletions + * * node creations + * * copy + * * move + * * renaming nodes + * + * If Tree classes implement a form of caching, this will allow + * them to make sure caches will be expired. + * + * If a path is passed, it is assumed that the entire subtree is dirty + * + * @param string $path + * @return void + */ + function markDirty($path) { + + // We don't care enough about sub-paths + // flushing the entire cache + $path = trim($path, '/'); + foreach ($this->cache as $nodePath => $node) { + if ($nodePath == $path || strpos($nodePath, $path . '/') === 0) + unset($this->cache[$nodePath]); + + } + + } + + /** + * This method tells the tree system to pre-fetch and cache a list of + * children of a single parent. + * + * There are a bunch of operations in the WebDAV stack that request many + * children (based on uris), and sometimes fetching many at once can + * optimize this. + * + * This method returns an array with the found nodes. It's keys are the + * original paths. The result may be out of order. + * + * @param array $paths List of nodes that must be fetched. + * @return array + */ + function getMultipleNodes($paths) { + + // Finding common parents + $parents = []; + foreach ($paths as $path) { + list($parent, $node) = URLUtil::splitPath($path); + if (!isset($parents[$parent])) { + $parents[$parent] = [$node]; + } else { + $parents[$parent][] = $node; + } + } + + $result = []; + + foreach ($parents as $parent => $children) { + + $parentNode = $this->getNodeForPath($parent); + if ($parentNode instanceof IMultiGet) { + foreach ($parentNode->getMultipleChildren($children) as $childNode) { + $fullPath = $parent . '/' . $childNode->getName(); + $result[$fullPath] = $childNode; + $this->cache[$fullPath] = $childNode; + } + } else { + foreach ($children as $child) { + $fullPath = $parent . '/' . $child; + $result[$fullPath] = $this->getNodeForPath($fullPath); + } + } + + } + + return $result; + + } + + + /** + * copyNode + * + * @param INode $source + * @param ICollection $destinationParent + * @param string $destinationName + * @return void + */ + protected function copyNode(INode $source, ICollection $destinationParent, $destinationName = null) { + + if (!$destinationName) $destinationName = $source->getName(); + + if ($source instanceof IFile) { + + $data = $source->get(); + + // If the body was a string, we need to convert it to a stream + if (is_string($data)) { + $stream = fopen('php://temp', 'r+'); + fwrite($stream, $data); + rewind($stream); + $data = $stream; + } + $destinationParent->createFile($destinationName, $data); + $destination = $destinationParent->getChild($destinationName); + + } elseif ($source instanceof ICollection) { + + $destinationParent->createDirectory($destinationName); + + $destination = $destinationParent->getChild($destinationName); + foreach ($source->getChildren() as $child) { + + $this->copyNode($child, $destination); + + } + + } + if ($source instanceof IProperties && $destination instanceof IProperties) { + + $props = $source->getProperties([]); + $propPatch = new PropPatch($props); + $destination->propPatch($propPatch); + $propPatch->commit(); + + } + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/UUIDUtil.php b/vendor/sabre/dav/lib/DAV/UUIDUtil.php new file mode 100644 index 000000000..177adafd3 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/UUIDUtil.php @@ -0,0 +1,64 @@ +value array. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Prop implements XmlDeserializable { + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + // If there's no children, we don't do anything. + if ($reader->isEmptyElement) { + $reader->next(); + return []; + } + + $values = []; + + $reader->read(); + do { + + if ($reader->nodeType === Reader::ELEMENT) { + + $clark = $reader->getClark(); + $values[$clark] = self::parseCurrentElement($reader)['value']; + + } else { + $reader->read(); + } + + } while ($reader->nodeType !== Reader::END_ELEMENT); + + $reader->read(); + + return $values; + + } + + /** + * This function behaves similar to Sabre\Xml\Reader::parseCurrentElement, + * but instead of creating deep xml array structures, it will turn any + * top-level element it doesn't recognize into either a string, or an + * XmlFragment class. + * + * This method returns arn array with 2 properties: + * * name - A clark-notation XML element name. + * * value - The parsed value. + * + * @param Reader $reader + * @return array + */ + private static function parseCurrentElement(Reader $reader) { + + $name = $reader->getClark(); + + if (array_key_exists($name, $reader->elementMap)) { + $deserializer = $reader->elementMap[$name]; + if (is_subclass_of($deserializer, 'Sabre\\Xml\\XmlDeserializable')) { + $value = call_user_func([ $deserializer, 'xmlDeserialize' ], $reader); + } elseif (is_callable($deserializer)) { + $value = call_user_func($deserializer, $reader); + } else { + $type = gettype($deserializer); + if ($type === 'string') { + $type .= ' (' . $deserializer . ')'; + } elseif ($type === 'object') { + $type .= ' (' . get_class($deserializer) . ')'; + } + throw new \LogicException('Could not use this type as a deserializer: ' . $type); + } + } else { + $value = Complex::xmlDeserialize($reader); + } + + return [ + 'name' => $name, + 'value' => $value, + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Element/Response.php b/vendor/sabre/dav/lib/DAV/Xml/Element/Response.php new file mode 100644 index 000000000..97a2bb59f --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Element/Response.php @@ -0,0 +1,253 @@ +href = $href; + $this->responseProperties = $responseProperties; + $this->httpStatus = $httpStatus; + + } + + /** + * Returns the url + * + * @return string + */ + function getHref() { + + return $this->href; + + } + + /** + * Returns the httpStatus value + * + * @return string + */ + function getHttpStatus() { + + return $this->httpStatus; + + } + + /** + * Returns the property list + * + * @return array + */ + function getResponseProperties() { + + return $this->responseProperties; + + } + + + /** + * The serialize method is called during xml writing. + * + * It should use the $writer argument to encode this object into XML. + * + * Important note: it is not needed to create the parent element. The + * parent element is already created, and we only have to worry about + * attributes, child elements and text (if any). + * + * Important note 2: If you are writing any new elements, you are also + * responsible for closing them. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + if ($status = $this->getHTTPStatus()) { + $writer->writeElement('{DAV:}status', 'HTTP/1.1 ' . $status . ' ' . \Sabre\HTTP\Response::$statusCodes[$status]); + } + $writer->writeElement('{DAV:}href', $writer->contextUri . \Sabre\HTTP\encodePath($this->getHref())); + + $empty = true; + + foreach ($this->getResponseProperties() as $status => $properties) { + + // Skipping empty lists + if (!$properties || (!ctype_digit($status) && !is_int($status))) { + continue; + } + $empty = false; + $writer->startElement('{DAV:}propstat'); + $writer->writeElement('{DAV:}prop', $properties); + $writer->writeElement('{DAV:}status', 'HTTP/1.1 ' . $status . ' ' . \Sabre\HTTP\Response::$statusCodes[$status]); + $writer->endElement(); // {DAV:}propstat + + } + if ($empty) { + /* + * The WebDAV spec _requires_ at least one DAV:propstat to appear for + * every DAV:response. In some circumstances however, there are no + * properties to encode. + * + * In those cases we MUST specify at least one DAV:propstat anyway, with + * no properties. + */ + $writer->writeElement('{DAV:}propstat', [ + '{DAV:}prop' => [], + '{DAV:}status' => 'HTTP/1.1 418 ' . \Sabre\HTTP\Response::$statusCodes[418] + ]); + + } + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $reader->pushContext(); + + $reader->elementMap['{DAV:}propstat'] = 'Sabre\\Xml\\Element\\KeyValue'; + + // We are overriding the parser for {DAV:}prop. This deserializer is + // almost identical to the one for Sabre\Xml\Element\KeyValue. + // + // The difference is that if there are any child-elements inside of + // {DAV:}prop, that have no value, normally any deserializers are + // called. But we don't want this, because a singular element without + // child-elements implies 'no value' in {DAV:}prop, so we want to skip + // deserializers and just set null for those. + $reader->elementMap['{DAV:}prop'] = function(Reader $reader) { + + if ($reader->isEmptyElement) { + $reader->next(); + return []; + } + $values = []; + $reader->read(); + do { + if ($reader->nodeType === Reader::ELEMENT) { + $clark = $reader->getClark(); + + if ($reader->isEmptyElement) { + $values[$clark] = null; + $reader->next(); + } else { + $values[$clark] = $reader->parseCurrentElement()['value']; + } + } else { + $reader->read(); + } + } while ($reader->nodeType !== Reader::END_ELEMENT); + $reader->read(); + return $values; + + }; + $elems = $reader->parseInnerTree(); + $reader->popContext(); + + $href = null; + $propertyLists = []; + $statusCode = null; + + foreach ($elems as $elem) { + + switch ($elem['name']) { + + case '{DAV:}href' : + $href = $elem['value']; + break; + case '{DAV:}propstat' : + $status = $elem['value']['{DAV:}status']; + list(, $status, ) = explode(' ', $status, 3); + $properties = isset($elem['value']['{DAV:}prop']) ? $elem['value']['{DAV:}prop'] : []; + if ($properties) $propertyLists[$status] = $properties; + break; + case '{DAV:}status' : + list(, $statusCode, ) = explode(' ', $elem['value'], 3); + break; + + } + + } + + return new self($href, $propertyLists, $statusCode); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/Complex.php b/vendor/sabre/dav/lib/DAV/Xml/Property/Complex.php new file mode 100644 index 000000000..1d9202082 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Property/Complex.php @@ -0,0 +1,89 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $xml = $reader->readInnerXml(); + + if ($reader->nodeType === Reader::ELEMENT && $reader->isEmptyElement) { + // Easy! + $reader->next(); + return null; + } + // Now we have a copy of the inner xml, we need to traverse it to get + // all the strings. If there's no non-string data, we just return the + // string, otherwise we return an instance of this class. + $reader->read(); + + $nonText = false; + $text = ''; + + while (true) { + + switch ($reader->nodeType) { + case Reader::ELEMENT : + $nonText = true; + $reader->next(); + continue 2; + case Reader::TEXT : + case Reader::CDATA : + $text .= $reader->value; + break; + case Reader::END_ELEMENT : + break 2; + } + $reader->read(); + + } + + // Make sure we advance the cursor one step further. + $reader->read(); + + if ($nonText) { + $new = new self($xml); + return $new; + } else { + return $text; + } + + } + + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/GetLastModified.php b/vendor/sabre/dav/lib/DAV/Xml/Property/GetLastModified.php new file mode 100644 index 000000000..2db47269f --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Property/GetLastModified.php @@ -0,0 +1,110 @@ +time = clone $time; + } else { + $this->time = new DateTime('@' . $time); + } + + // Setting timezone to UTC + $this->time->setTimezone(new DateTimeZone('UTC')); + + } + + /** + * getTime + * + * @return DateTime + */ + function getTime() { + + return $this->time; + + } + + /** + * The serialize method is called during xml writing. + * + * It should use the $writer argument to encode this object into XML. + * + * Important note: it is not needed to create the parent element. The + * parent element is already created, and we only have to worry about + * attributes, child elements and text (if any). + * + * Important note 2: If you are writing any new elements, you are also + * responsible for closing them. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + $writer->write( + HTTP\Util::toHTTPDate($this->time) + ); + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * Important note 2: You are responsible for advancing the reader to the + * next element. Not doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + return + new self(new DateTime($reader->parseInnerTree())); + + } +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php b/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php new file mode 100644 index 000000000..538e98d0f --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php @@ -0,0 +1,176 @@ +hrefs = $hrefs; + $this->autoPrefix = $autoPrefix; + + + } + + /** + * Returns the first Href. + * + * @return string + */ + function getHref() { + + return $this->hrefs[0]; + + } + + /** + * Returns the hrefs as an array + * + * @return array + */ + function getHrefs() { + + return $this->hrefs; + + } + + /** + * The xmlSerialize metod is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->getHrefs() as $href) { + if ($this->autoPrefix) { + $href = $writer->contextUri . \Sabre\HTTP\encodePath($href); + } + $writer->writeElement('{DAV:}href', $href); + } + + } + + /** + * Generate html representation for this value. + * + * The html output is 100% trusted, and no effort is being made to sanitize + * it. It's up to the implementor to sanitize user provided values. + * + * The output must be in UTF-8. + * + * The baseUri parameter is a url to the root of the application, and can + * be used to construct local links. + * + * @param HtmlOutputHelper $html + * @return string + */ + function toHtml(HtmlOutputHelper $html) { + + $links = []; + foreach ($this->getHrefs() as $href) { + $links[] = $html->link($href); + } + return implode('
', $links); + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $hrefs = []; + foreach ((array)$reader->parseInnerTree() as $elem) { + if ($elem['name'] !== '{DAV:}href') + continue; + + $hrefs[] = $elem['value']; + + } + if ($hrefs) { + return new self($hrefs, false); + } + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/LockDiscovery.php b/vendor/sabre/dav/lib/DAV/Xml/Property/LockDiscovery.php new file mode 100644 index 000000000..f4b692219 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Property/LockDiscovery.php @@ -0,0 +1,106 @@ +locks = $locks; + + } + + /** + * The serialize method is called during xml writing. + * + * It should use the $writer argument to encode this object into XML. + * + * Important note: it is not needed to create the parent element. The + * parent element is already created, and we only have to worry about + * attributes, child elements and text (if any). + * + * Important note 2: If you are writing any new elements, you are also + * responsible for closing them. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->locks as $lock) { + + $writer->startElement('{DAV:}activelock'); + + $writer->startElement('{DAV:}lockscope'); + if ($lock->scope === LockInfo::SHARED) { + $writer->writeElement('{DAV:}shared'); + } else { + $writer->writeElement('{DAV:}exclusive'); + } + + $writer->endElement(); // {DAV:}lockscope + + $writer->startElement('{DAV:}locktype'); + $writer->writeElement('{DAV:}write'); + $writer->endElement(); // {DAV:}locktype + + if (!self::$hideLockRoot) { + $writer->startElement('{DAV:}lockroot'); + $writer->writeElement('{DAV:}href', $writer->contextUri . $lock->uri); + $writer->endElement(); // {DAV:}lockroot + } + $writer->writeElement('{DAV:}depth', ($lock->depth == DAV\Server::DEPTH_INFINITY ? 'infinity' : $lock->depth)); + $writer->writeElement('{DAV:}timeout', 'Second-' . $lock->timeout); + + $writer->startElement('{DAV:}locktoken'); + $writer->writeElement('{DAV:}href', 'opaquelocktoken:' . $lock->token); + $writer->endElement(); // {DAV:}locktoken + + $writer->writeElement('{DAV:}owner', new XmlFragment($lock->owner)); + $writer->endElement(); // {DAV:}activelock + + } + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/ResourceType.php b/vendor/sabre/dav/lib/DAV/Xml/Property/ResourceType.php new file mode 100644 index 000000000..302888321 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Property/ResourceType.php @@ -0,0 +1,128 @@ +value; + + } + + /** + * Checks if the principal contains a certain value + * + * @param string $type + * @return bool + */ + function is($type) { + + return in_array($type, $this->value); + + } + + /** + * Adds a resourcetype value to this property + * + * @param string $type + * @return void + */ + function add($type) { + + $this->value[] = $type; + $this->value = array_unique($this->value); + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * Important note 2: You are responsible for advancing the reader to the + * next element. Not doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + return + new self(parent::xmlDeserialize($reader)); + + } + + /** + * Generate html representation for this value. + * + * The html output is 100% trusted, and no effort is being made to sanitize + * it. It's up to the implementor to sanitize user provided values. + * + * The output must be in UTF-8. + * + * The baseUri parameter is a url to the root of the application, and can + * be used to construct local links. + * + * @param HtmlOutputHelper $html + * @return string + */ + function toHtml(HtmlOutputHelper $html) { + + return implode( + ', ', + array_map([$html, 'xmlName'], $this->getValue()) + ); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedLock.php b/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedLock.php new file mode 100644 index 000000000..f6d01aa37 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedLock.php @@ -0,0 +1,54 @@ +writeElement('{DAV:}lockentry', [ + '{DAV:}lockscope' => ['{DAV:}exclusive' => null], + '{DAV:}locktype' => ['{DAV:}write' => null], + ]); + $writer->writeElement('{DAV:}lockentry', [ + '{DAV:}lockscope' => ['{DAV:}shared' => null], + '{DAV:}locktype' => ['{DAV:}write' => null], + ]); + + } +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedMethodSet.php b/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedMethodSet.php new file mode 100644 index 000000000..56b418db6 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedMethodSet.php @@ -0,0 +1,126 @@ +methods = (array)$methods; + + } + + /** + * Returns the list of supported http methods. + * + * @return string[] + */ + function getValue() { + + return $this->methods; + + } + + /** + * Returns true or false if the property contains a specific method. + * + * @param string $methodName + * @return bool + */ + function has($methodName) { + + return in_array( + $methodName, + $this->methods + ); + + } + + /** + * The xmlSerialize metod is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->getValue() as $val) { + $writer->startElement('{DAV:}supported-method'); + $writer->writeAttribute('name', $val); + $writer->endElement(); + } + + } + + /** + * Generate html representation for this value. + * + * The html output is 100% trusted, and no effort is being made to sanitize + * it. It's up to the implementor to sanitize user provided values. + * + * The output must be in UTF-8. + * + * The baseUri parameter is a url to the root of the application, and can + * be used to construct local links. + * + * @param HtmlOutputHelper $html + * @return string + */ + function toHtml(HtmlOutputHelper $html) { + + return implode( + ', ', + array_map([$html, 'h'], $this->getValue()) + ); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedReportSet.php b/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedReportSet.php new file mode 100644 index 000000000..ebf27300d --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedReportSet.php @@ -0,0 +1,154 @@ +addReport($reports); + + } + + /** + * Adds a report to this property + * + * The report must be a string in clark-notation. + * Multiple reports can be specified as an array. + * + * @param mixed $report + * @return void + */ + function addReport($report) { + + $report = (array)$report; + + foreach ($report as $r) { + + if (!preg_match('/^{([^}]*)}(.*)$/', $r)) + throw new DAV\Exception('Reportname must be in clark-notation'); + + $this->reports[] = $r; + + } + + } + + /** + * Returns the list of supported reports + * + * @return string[] + */ + function getValue() { + + return $this->reports; + + } + + /** + * Returns true or false if the property contains a specific report. + * + * @param string $reportName + * @return bool + */ + function has($reportName) { + + return in_array( + $reportName, + $this->reports + ); + + } + + /** + * The xmlSerialize metod is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->getValue() as $val) { + $writer->startElement('{DAV:}supported-report'); + $writer->startElement('{DAV:}report'); + $writer->writeElement($val); + $writer->endElement(); + $writer->endElement(); + } + + } + + /** + * Generate html representation for this value. + * + * The html output is 100% trusted, and no effort is being made to sanitize + * it. It's up to the implementor to sanitize user provided values. + * + * The output must be in UTF-8. + * + * The baseUri parameter is a url to the root of the application, and can + * be used to construct local links. + * + * @param HtmlOutputHelper $html + * @return string + */ + function toHtml(HtmlOutputHelper $html) { + + return implode( + ', ', + array_map([$html, 'xmlName'], $this->getValue()) + ); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Request/Lock.php b/vendor/sabre/dav/lib/DAV/Xml/Request/Lock.php new file mode 100644 index 000000000..76df98d13 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Request/Lock.php @@ -0,0 +1,81 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $reader->pushContext(); + $reader->elementMap['{DAV:}owner'] = 'Sabre\\Xml\\Element\\XmlFragment'; + + $values = KeyValue::xmlDeserialize($reader); + + $reader->popContext(); + + $new = new self(); + $new->owner = !empty($values['{DAV:}owner']) ? $values['{DAV:}owner']->getXml() : null; + $new->scope = LockInfo::SHARED; + + if (isset($values['{DAV:}lockscope'])) { + foreach ($values['{DAV:}lockscope'] as $elem) { + if ($elem['name'] === '{DAV:}exclusive') $new->scope = LockInfo::EXCLUSIVE; + } + } + return $new; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Request/MkCol.php b/vendor/sabre/dav/lib/DAV/Xml/Request/MkCol.php new file mode 100644 index 000000000..5db239061 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Request/MkCol.php @@ -0,0 +1,82 @@ +value array with properties that are supposed to get set + * during creation of the new collection. + * + * @return array + */ + function getProperties() { + + return $this->properties; + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $self = new self(); + + $elementMap = $reader->elementMap; + $elementMap['{DAV:}prop'] = 'Sabre\DAV\Xml\Element\Prop'; + $elementMap['{DAV:}set'] = 'Sabre\Xml\Element\KeyValue'; + $elementMap['{DAV:}remove'] = 'Sabre\Xml\Element\KeyValue'; + + $elems = $reader->parseInnerTree($elementMap); + + foreach ($elems as $elem) { + if ($elem['name'] === '{DAV:}set') { + $self->properties = array_merge($self->properties, $elem['value']['{DAV:}prop']); + } + } + + return $self; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Request/PropFind.php b/vendor/sabre/dav/lib/DAV/Xml/Request/PropFind.php new file mode 100644 index 000000000..ad3ad7c43 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Request/PropFind.php @@ -0,0 +1,83 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $self = new self(); + + $reader->pushContext(); + $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Element\Elements'; + + foreach (KeyValue::xmlDeserialize($reader) as $k => $v) { + + switch ($k) { + case '{DAV:}prop' : + $self->properties = $v; + break; + case '{DAV:}allprop' : + $self->allProp = true; + + } + + } + + $reader->popContext(); + + return $self; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Request/PropPatch.php b/vendor/sabre/dav/lib/DAV/Xml/Request/PropPatch.php new file mode 100644 index 000000000..07a05f887 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Request/PropPatch.php @@ -0,0 +1,118 @@ +properties as $propertyName => $propertyValue) { + + if (is_null($propertyValue)) { + $writer->startElement("{DAV:}remove"); + $writer->write(['{DAV:}prop' => [$propertyName => $propertyValue]]); + $writer->endElement(); + } else { + $writer->startElement("{DAV:}set"); + $writer->write(['{DAV:}prop' => [$propertyName => $propertyValue]]); + $writer->endElement(); + } + + } + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $self = new self(); + + $elementMap = $reader->elementMap; + $elementMap['{DAV:}prop'] = 'Sabre\DAV\Xml\Element\Prop'; + $elementMap['{DAV:}set'] = 'Sabre\Xml\Element\KeyValue'; + $elementMap['{DAV:}remove'] = 'Sabre\Xml\Element\KeyValue'; + + $elems = $reader->parseInnerTree($elementMap); + + foreach ($elems as $elem) { + if ($elem['name'] === '{DAV:}set') { + $self->properties = array_merge($self->properties, $elem['value']['{DAV:}prop']); + } + if ($elem['name'] === '{DAV:}remove') { + + // Ensuring there are no values. + foreach ($elem['value']['{DAV:}prop'] as $remove => $value) { + $self->properties[$remove] = null; + } + + } + } + + return $self; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Request/SyncCollectionReport.php b/vendor/sabre/dav/lib/DAV/Xml/Request/SyncCollectionReport.php new file mode 100644 index 000000000..3092ada47 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Request/SyncCollectionReport.php @@ -0,0 +1,122 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $self = new self(); + + $reader->pushContext(); + + $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Element\Elements'; + $elems = KeyValue::xmlDeserialize($reader); + + $reader->popContext(); + + $required = [ + '{DAV:}sync-token', + '{DAV:}prop', + ]; + + foreach ($required as $elem) { + if (!array_key_exists($elem, $elems)) { + throw new BadRequest('The ' . $elem . ' element in the {DAV:}sync-collection report is required'); + } + } + + + $self->properties = $elems['{DAV:}prop']; + $self->syncToken = $elems['{DAV:}sync-token']; + + if (isset($elems['{DAV:}limit'])) { + $nresults = null; + foreach ($elems['{DAV:}limit'] as $child) { + if ($child['name'] === '{DAV:}nresults') { + $nresults = (int)$child['value']; + } + } + $self->limit = $nresults; + } + + if (isset($elems['{DAV:}sync-level'])) { + + $value = $elems['{DAV:}sync-level']; + if ($value === 'infinity') { + $value = \Sabre\DAV\Server::DEPTH_INFINITY; + } + $self->syncLevel = $value; + + } + + return $self; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Response/MultiStatus.php b/vendor/sabre/dav/lib/DAV/Xml/Response/MultiStatus.php new file mode 100644 index 000000000..16a3d4a68 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Response/MultiStatus.php @@ -0,0 +1,142 @@ +responses = $responses; + $this->syncToken = $syncToken; + + } + + /** + * Returns the response list. + * + * @return \Sabre\DAV\Xml\Element\Response[] + */ + function getResponses() { + + return $this->responses; + + } + + /** + * Returns the sync-token, if available. + * + * @return string|null + */ + function getSyncToken() { + + return $this->syncToken; + + } + + /** + * The serialize method is called during xml writing. + * + * It should use the $writer argument to encode this object into XML. + * + * Important note: it is not needed to create the parent element. The + * parent element is already created, and we only have to worry about + * attributes, child elements and text (if any). + * + * Important note 2: If you are writing any new elements, you are also + * responsible for closing them. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->getResponses() as $response) { + $writer->writeElement('{DAV:}response', $response); + } + if ($syncToken = $this->getSyncToken()) { + $writer->writeElement('{DAV:}sync-token', $syncToken); + } + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elementMap = $reader->elementMap; + $elementMap['{DAV:}prop'] = 'Sabre\\DAV\\Xml\\Element\\Prop'; + $elements = $reader->parseInnerTree($elementMap); + + $responses = []; + $syncToken = null; + + if ($elements) foreach ($elements as $elem) { + if ($elem['name'] === '{DAV:}response') { + $responses[] = $elem['value']; + } + if ($elem['name'] === '{DAV:}sync-token') { + $syncToken = $elem['value']; + } + } + + return new self($responses, $syncToken); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Service.php b/vendor/sabre/dav/lib/DAV/Xml/Service.php new file mode 100644 index 000000000..f41ed984a --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Service.php @@ -0,0 +1,47 @@ + 'Sabre\\DAV\\Xml\\Response\\MultiStatus', + '{DAV:}response' => 'Sabre\\DAV\\Xml\\Element\\Response', + + // Requests + '{DAV:}propfind' => 'Sabre\\DAV\\Xml\\Request\\PropFind', + '{DAV:}propertyupdate' => 'Sabre\\DAV\\Xml\\Request\\PropPatch', + '{DAV:}mkcol' => 'Sabre\\DAV\\Xml\\Request\\MkCol', + + // Properties + '{DAV:}resourcetype' => 'Sabre\\DAV\\Xml\\Property\\ResourceType', + + ]; + + /** + * This is a default list of namespaces. + * + * If you are defining your own custom namespace, add it here to reduce + * bandwidth and improve legibility of xml bodies. + * + * @var array + */ + public $namespaceMap = [ + 'DAV:' => 'd', + 'http://sabredav.org/ns' => 's', + ]; + +} diff --git a/vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php b/vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php new file mode 100644 index 000000000..460f78981 --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php @@ -0,0 +1,181 @@ +principalPrefix = $principalPrefix; + $this->principalBackend = $principalBackend; + + } + + /** + * This method returns a node for a principal. + * + * The passed array contains principal information, and is guaranteed to + * at least contain a uri item. Other properties may or may not be + * supplied by the authentication backend. + * + * @param array $principalInfo + * @return IPrincipal + */ + abstract function getChildForPrincipal(array $principalInfo); + + /** + * Returns the name of this collection. + * + * @return string + */ + function getName() { + + list(, $name) = URLUtil::splitPath($this->principalPrefix); + return $name; + + } + + /** + * Return the list of users + * + * @return array + */ + function getChildren() { + + if ($this->disableListing) + throw new DAV\Exception\MethodNotAllowed('Listing members of this collection is disabled'); + + $children = []; + foreach ($this->principalBackend->getPrincipalsByPrefix($this->principalPrefix) as $principalInfo) { + + $children[] = $this->getChildForPrincipal($principalInfo); + + + } + return $children; + + } + + /** + * Returns a child object, by its name. + * + * @param string $name + * @throws DAV\Exception\NotFound + * @return IPrincipal + */ + function getChild($name) { + + $principalInfo = $this->principalBackend->getPrincipalByPath($this->principalPrefix . '/' . $name); + if (!$principalInfo) throw new DAV\Exception\NotFound('Principal with name ' . $name . ' not found'); + return $this->getChildForPrincipal($principalInfo); + + } + + /** + * This method is used to search for principals matching a set of + * properties. + * + * This search is specifically used by RFC3744's principal-property-search + * REPORT. You should at least allow searching on + * http://sabredav.org/ns}email-address. + * + * The actual search should be a unicode-non-case-sensitive search. The + * keys in searchProperties are the WebDAV property names, while the values + * are the property values to search on. + * + * By default, if multiple properties are submitted to this method, the + * various properties should be combined with 'AND'. If $test is set to + * 'anyof', it should be combined using 'OR'. + * + * This method should simply return a list of 'child names', which may be + * used to call $this->getChild in the future. + * + * @param array $searchProperties + * @param string $test + * @return array + */ + function searchPrincipals(array $searchProperties, $test = 'allof') { + + $result = $this->principalBackend->searchPrincipals($this->principalPrefix, $searchProperties, $test); + $r = []; + + foreach ($result as $row) { + list(, $r[]) = URLUtil::splitPath($row); + } + + return $r; + + } + + /** + * Finds a principal by its URI. + * + * This method may receive any type of uri, but mailto: addresses will be + * the most common. + * + * Implementation of this API is optional. It is currently used by the + * CalDAV system to find principals based on their email addresses. If this + * API is not implemented, some features may not work correctly. + * + * This method must return a relative principal path, or null, if the + * principal was not found or you refuse to find it. + * + * @param string $uri + * @return string + */ + function findByUri($uri) { + + return $this->principalBackend->findByUri($uri, $this->principalPrefix); + + } + +} diff --git a/vendor/sabre/dav/lib/DAVACL/Exception/AceConflict.php b/vendor/sabre/dav/lib/DAVACL/Exception/AceConflict.php new file mode 100644 index 000000000..22450b4a6 --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/Exception/AceConflict.php @@ -0,0 +1,35 @@ +ownerDocument; + + $np = $doc->createElementNS('DAV:', 'd:no-ace-conflict'); + $errorNode->appendChild($np); + + } + +} diff --git a/vendor/sabre/dav/lib/DAVACL/Exception/NeedPrivileges.php b/vendor/sabre/dav/lib/DAVACL/Exception/NeedPrivileges.php new file mode 100644 index 000000000..5624fd22f --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/Exception/NeedPrivileges.php @@ -0,0 +1,82 @@ +uri = $uri; + $this->privileges = $privileges; + + parent::__construct('User did not have the required privileges (' . implode(',', $privileges) . ') for path "' . $uri . '"'); + + } + + /** + * Adds in extra information in the xml response. + * + * This method adds the {DAV:}need-privileges element as defined in rfc3744 + * + * @param DAV\Server $server + * @param \DOMElement $errorNode + * @return void + */ + function serialize(DAV\Server $server, \DOMElement $errorNode) { + + $doc = $errorNode->ownerDocument; + + $np = $doc->createElementNS('DAV:', 'd:need-privileges'); + $errorNode->appendChild($np); + + foreach ($this->privileges as $privilege) { + + $resource = $doc->createElementNS('DAV:', 'd:resource'); + $np->appendChild($resource); + + $resource->appendChild($doc->createElementNS('DAV:', 'd:href', $server->getBaseUri() . $this->uri)); + + $priv = $doc->createElementNS('DAV:', 'd:privilege'); + $resource->appendChild($priv); + + preg_match('/^{([^}]*)}(.*)$/', $privilege, $privilegeParts); + $priv->appendChild($doc->createElementNS($privilegeParts[1], 'd:' . $privilegeParts[2])); + + + } + + } + +} diff --git a/vendor/sabre/dav/lib/DAVACL/Exception/NoAbstract.php b/vendor/sabre/dav/lib/DAVACL/Exception/NoAbstract.php new file mode 100644 index 000000000..a2363b174 --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/Exception/NoAbstract.php @@ -0,0 +1,35 @@ +ownerDocument; + + $np = $doc->createElementNS('DAV:', 'd:no-abstract'); + $errorNode->appendChild($np); + + } + +} diff --git a/vendor/sabre/dav/lib/DAVACL/Exception/NotRecognizedPrincipal.php b/vendor/sabre/dav/lib/DAVACL/Exception/NotRecognizedPrincipal.php new file mode 100644 index 000000000..4349bf101 --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/Exception/NotRecognizedPrincipal.php @@ -0,0 +1,35 @@ +ownerDocument; + + $np = $doc->createElementNS('DAV:', 'd:recognized-principal'); + $errorNode->appendChild($np); + + } + +} diff --git a/vendor/sabre/dav/lib/DAVACL/Exception/NotSupportedPrivilege.php b/vendor/sabre/dav/lib/DAVACL/Exception/NotSupportedPrivilege.php new file mode 100644 index 000000000..73b81190d --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/Exception/NotSupportedPrivilege.php @@ -0,0 +1,35 @@ +ownerDocument; + + $np = $doc->createElementNS('DAV:', 'd:not-supported-privilege'); + $errorNode->appendChild($np); + + } + +} diff --git a/vendor/sabre/dav/lib/DAVACL/FS/Collection.php b/vendor/sabre/dav/lib/DAVACL/FS/Collection.php new file mode 100644 index 000000000..5fab4768c --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/FS/Collection.php @@ -0,0 +1,153 @@ +acl = $acl; + $this->owner = $owner; + + } + + /** + * Returns a specific child node, referenced by its name + * + * This method must throw Sabre\DAV\Exception\NotFound if the node does not + * exist. + * + * @param string $name + * @throws DAV\Exception\NotFound + * @return DAV\INode + */ + function getChild($name) { + + $path = $this->path . '/' . $name; + + if (!file_exists($path)) throw new NotFound('File could not be located'); + if ($name == '.' || $name == '..') throw new Forbidden('Permission denied to . and ..'); + + if (is_dir($path)) { + + return new self($path, $this->acl, $this->owner); + + } else { + + return new File($path, $this->acl, $this->owner); + + } + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->owner; + + } + + /** + * Returns a group principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getGroup() { + + return null; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + return $this->acl; + + } + + /** + * Updates the ACL + * + * This method will receive a list of new ACE's as an array argument. + * + * @param array $acl + * @return void + */ + function setACL(array $acl) { + + throw new Forbidden('Setting ACL is not allowed here'); + + } + + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + function getSupportedPrivilegeSet() { + + return null; + + } + +} diff --git a/vendor/sabre/dav/lib/DAVACL/FS/File.php b/vendor/sabre/dav/lib/DAVACL/FS/File.php new file mode 100644 index 000000000..0d549528b --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/FS/File.php @@ -0,0 +1,123 @@ +acl = $acl; + $this->owner = $owner; + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->owner; + + } + + /** + * Returns a group principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getGroup() { + + return null; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + return $this->acl; + + } + + /** + * Updates the ACL + * + * This method will receive a list of new ACE's as an array argument. + * + * @param array $acl + * @return void + */ + function setACL(array $acl) { + + throw new Forbidden('Setting ACL is not allowed here'); + + } + + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + function getSupportedPrivilegeSet() { + + return null; + + } + +} diff --git a/vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php b/vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php new file mode 100644 index 000000000..c27616770 --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php @@ -0,0 +1,188 @@ +storagePath = $storagePath; + + } + + /** + * Returns the name of the node. + * + * This is used to generate the url. + * + * @return string + */ + function getName() { + + return $this->collectionName; + + } + + /** + * Returns a principals' collection of files. + * + * The passed array contains principal information, and is guaranteed to + * at least contain a uri item. Other properties may or may not be + * supplied by the authentication backend. + * + * @param array $principalInfo + * @return void + */ + function getChildForPrincipal(array $principalInfo) { + + $owner = $principalInfo['uri']; + $acl = [ + [ + 'privilege' => '{DAV:}read', + 'principal' => $owner, + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => $owner, + 'protected' => true, + ], + ]; + + list(, $principalBaseName) = Uri\split($owner); + + $path = $this->storagePath . '/' . $principalBaseName; + + if (!is_dir($path)) { + mkdir($path, 0777, true); + } + return new Collection( + $path, + $acl, + $owner + ); + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return null; + + } + + /** + * Returns a group principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getGroup() { + + return null; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + return [ + [ + 'principal' => '{DAV:}authenticated', + 'privilege' => '{DAV:}read', + 'protected' => true, + ] + ]; + + } + + /** + * Updates the ACL + * + * This method will receive a list of new ACE's as an array argument. + * + * @param array $acl + * @return void + */ + function setACL(array $acl) { + + throw new Forbidden('Setting ACL is not allowed here'); + + } + + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + function getSupportedPrivilegeSet() { + + return null; + + } + +} diff --git a/vendor/sabre/dav/lib/DAVACL/IACL.php b/vendor/sabre/dav/lib/DAVACL/IACL.php new file mode 100644 index 000000000..81908d08f --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/IACL.php @@ -0,0 +1,75 @@ +getChild in the future. + * + * @param array $searchProperties + * @param string $test + * @return array + */ + function searchPrincipals(array $searchProperties, $test = 'allof'); + + /** + * Finds a principal by its URI. + * + * This method may receive any type of uri, but mailto: addresses will be + * the most common. + * + * Implementation of this API is optional. It is currently used by the + * CalDAV system to find principals based on their email addresses. If this + * API is not implemented, some features may not work correctly. + * + * This method must return a relative principal path, or null, if the + * principal was not found or you refuse to find it. + * + * @param string $uri + * @return string + */ + function findByUri($uri); + +} diff --git a/vendor/sabre/dav/lib/DAVACL/Plugin.php b/vendor/sabre/dav/lib/DAVACL/Plugin.php new file mode 100644 index 000000000..601dffecc --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/Plugin.php @@ -0,0 +1,1323 @@ + 'Display name', + '{http://sabredav.org/ns}email-address' => 'Email address', + ]; + + /** + * Any principal uri's added here, will automatically be added to the list + * of ACL's. They will effectively receive {DAV:}all privileges, as a + * protected privilege. + * + * @var array + */ + public $adminPrincipals = []; + + /** + * Returns a list of features added by this plugin. + * + * This list is used in the response of a HTTP OPTIONS request. + * + * @return array + */ + function getFeatures() { + + return ['access-control', 'calendarserver-principal-property-search']; + + } + + /** + * Returns a list of available methods for a given url + * + * @param string $uri + * @return array + */ + function getMethods($uri) { + + return ['ACL']; + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using Sabre\DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'acl'; + + } + + /** + * Returns a list of reports this plugin supports. + * + * This will be used in the {DAV:}supported-report-set property. + * Note that you still need to subscribe to the 'report' event to actually + * implement them + * + * @param string $uri + * @return array + */ + function getSupportedReportSet($uri) { + + return [ + '{DAV:}expand-property', + '{DAV:}principal-property-search', + '{DAV:}principal-search-property-set', + ]; + + } + + + /** + * Checks if the current user has the specified privilege(s). + * + * You can specify a single privilege, or a list of privileges. + * This method will throw an exception if the privilege is not available + * and return true otherwise. + * + * @param string $uri + * @param array|string $privileges + * @param int $recursion + * @param bool $throwExceptions if set to false, this method won't throw exceptions. + * @throws Sabre\DAVACL\Exception\NeedPrivileges + * @return bool + */ + function checkPrivileges($uri, $privileges, $recursion = self::R_PARENT, $throwExceptions = true) { + + if (!is_array($privileges)) $privileges = [$privileges]; + + $acl = $this->getCurrentUserPrivilegeSet($uri); + + if (is_null($acl)) { + if ($this->allowAccessToNodesWithoutACL) { + return true; + } else { + if ($throwExceptions) + throw new Exception\NeedPrivileges($uri, $privileges); + else + return false; + + } + } + + $failed = []; + foreach ($privileges as $priv) { + + if (!in_array($priv, $acl)) { + $failed[] = $priv; + } + + } + + if ($failed) { + if ($throwExceptions) + throw new Exception\NeedPrivileges($uri, $failed); + else + return false; + } + return true; + + } + + /** + * Returns the standard users' principal. + * + * This is one authorative principal url for the current user. + * This method will return null if the user wasn't logged in. + * + * @return string|null + */ + function getCurrentUserPrincipal() { + + $authPlugin = $this->server->getPlugin('auth'); + if (is_null($authPlugin)) return null; + /** @var $authPlugin Sabre\DAV\Auth\Plugin */ + + return $authPlugin->getCurrentPrincipal(); + + } + + + /** + * Returns a list of principals that's associated to the current + * user, either directly or through group membership. + * + * @return array + */ + function getCurrentUserPrincipals() { + + $currentUser = $this->getCurrentUserPrincipal(); + + if (is_null($currentUser)) return []; + + return array_merge( + [$currentUser], + $this->getPrincipalMembership($currentUser) + ); + + } + + /** + * This array holds a cache for all the principals that are associated with + * a single principal. + * + * @var array + */ + protected $principalMembershipCache = []; + + + /** + * Returns all the principal groups the specified principal is a member of. + * + * @param string $principal + * @return array + */ + function getPrincipalMembership($mainPrincipal) { + + // First check our cache + if (isset($this->principalMembershipCache[$mainPrincipal])) { + return $this->principalMembershipCache[$mainPrincipal]; + } + + $check = [$mainPrincipal]; + $principals = []; + + while (count($check)) { + + $principal = array_shift($check); + + $node = $this->server->tree->getNodeForPath($principal); + if ($node instanceof IPrincipal) { + foreach ($node->getGroupMembership() as $groupMember) { + + if (!in_array($groupMember, $principals)) { + + $check[] = $groupMember; + $principals[] = $groupMember; + + } + + } + + } + + } + + // Store the result in the cache + $this->principalMembershipCache[$mainPrincipal] = $principals; + + return $principals; + + } + + /** + * Returns the supported privilege structure for this ACL plugin. + * + * See RFC3744 for more details. Currently we default on a simple, + * standard structure. + * + * You can either get the list of privileges by a uri (path) or by + * specifying a Node. + * + * @param string|INode $node + * @return array + */ + function getSupportedPrivilegeSet($node) { + + if (is_string($node)) { + $node = $this->server->tree->getNodeForPath($node); + } + + if ($node instanceof IACL) { + $result = $node->getSupportedPrivilegeSet(); + + if ($result) + return $result; + } + + return self::getDefaultSupportedPrivilegeSet(); + + } + + /** + * Returns a fairly standard set of privileges, which may be useful for + * other systems to use as a basis. + * + * @return array + */ + static function getDefaultSupportedPrivilegeSet() { + + return [ + 'privilege' => '{DAV:}all', + 'abstract' => true, + 'aggregates' => [ + [ + 'privilege' => '{DAV:}read', + 'aggregates' => [ + [ + 'privilege' => '{DAV:}read-acl', + 'abstract' => false, + ], + [ + 'privilege' => '{DAV:}read-current-user-privilege-set', + 'abstract' => false, + ], + ], + ], // {DAV:}read + [ + 'privilege' => '{DAV:}write', + 'aggregates' => [ + [ + 'privilege' => '{DAV:}write-acl', + 'abstract' => false, + ], + [ + 'privilege' => '{DAV:}write-properties', + 'abstract' => false, + ], + [ + 'privilege' => '{DAV:}write-content', + 'abstract' => false, + ], + [ + 'privilege' => '{DAV:}bind', + 'abstract' => false, + ], + [ + 'privilege' => '{DAV:}unbind', + 'abstract' => false, + ], + [ + 'privilege' => '{DAV:}unlock', + 'abstract' => false, + ], + ], + ], // {DAV:}write + ], + ]; // {DAV:}all + + } + + /** + * Returns the supported privilege set as a flat list + * + * This is much easier to parse. + * + * The returned list will be index by privilege name. + * The value is a struct containing the following properties: + * - aggregates + * - abstract + * - concrete + * + * @param string|INode $node + * @return array + */ + final function getFlatPrivilegeSet($node) { + + $privs = $this->getSupportedPrivilegeSet($node); + + $fpsTraverse = null; + $fpsTraverse = function($priv, $concrete, &$flat) use (&$fpsTraverse) { + + $myPriv = [ + 'privilege' => $priv['privilege'], + 'abstract' => isset($priv['abstract']) && $priv['abstract'], + 'aggregates' => [], + 'concrete' => isset($priv['abstract']) && $priv['abstract'] ? $concrete : $priv['privilege'], + ]; + + if (isset($priv['aggregates'])) { + + foreach ($priv['aggregates'] as $subPriv) { + + $myPriv['aggregates'][] = $subPriv['privilege']; + + } + + } + + $flat[$priv['privilege']] = $myPriv; + + if (isset($priv['aggregates'])) { + + foreach ($priv['aggregates'] as $subPriv) { + + $fpsTraverse($subPriv, $myPriv['concrete'], $flat); + + } + + } + + }; + + $flat = []; + $fpsTraverse($privs, null, $flat); + + return $flat; + + } + + /** + * Returns the full ACL list. + * + * Either a uri or a INode may be passed. + * + * null will be returned if the node doesn't support ACLs. + * + * @param string|DAV\INode $node + * @return array + */ + function getACL($node) { + + if (is_string($node)) { + $node = $this->server->tree->getNodeForPath($node); + } + if (!$node instanceof IACL) { + return null; + } + $acl = $node->getACL(); + foreach ($this->adminPrincipals as $adminPrincipal) { + $acl[] = [ + 'principal' => $adminPrincipal, + 'privilege' => '{DAV:}all', + 'protected' => true, + ]; + } + return $acl; + + } + + /** + * Returns a list of privileges the current user has + * on a particular node. + * + * Either a uri or a DAV\INode may be passed. + * + * null will be returned if the node doesn't support ACLs. + * + * @param string|DAV\INode $node + * @return array + */ + function getCurrentUserPrivilegeSet($node) { + + if (is_string($node)) { + $node = $this->server->tree->getNodeForPath($node); + } + + $acl = $this->getACL($node); + + if (is_null($acl)) return null; + + $principals = $this->getCurrentUserPrincipals(); + + $collected = []; + + foreach ($acl as $ace) { + + $principal = $ace['principal']; + + switch ($principal) { + + case '{DAV:}owner' : + $owner = $node->getOwner(); + if ($owner && in_array($owner, $principals)) { + $collected[] = $ace; + } + break; + + + // 'all' matches for every user + case '{DAV:}all' : + + // 'authenticated' matched for every user that's logged in. + // Since it's not possible to use ACL while not being logged + // in, this is also always true. + case '{DAV:}authenticated' : + $collected[] = $ace; + break; + + // 'unauthenticated' can never occur either, so we simply + // ignore these. + case '{DAV:}unauthenticated' : + break; + + default : + if (in_array($ace['principal'], $principals)) { + $collected[] = $ace; + } + break; + + } + + + } + + // Now we deduct all aggregated privileges. + $flat = $this->getFlatPrivilegeSet($node); + + $collected2 = []; + while (count($collected)) { + + $current = array_pop($collected); + $collected2[] = $current['privilege']; + + foreach ($flat[$current['privilege']]['aggregates'] as $subPriv) { + $collected2[] = $subPriv; + $collected[] = $flat[$subPriv]; + } + + } + + return array_values(array_unique($collected2)); + + } + + + /** + * Returns a principal based on its uri. + * + * Returns null if the principal could not be found. + * + * @param string $uri + * @return null|string + */ + function getPrincipalByUri($uri) { + + $result = null; + $collections = $this->principalCollectionSet; + foreach ($collections as $collection) { + + $principalCollection = $this->server->tree->getNodeForPath($collection); + if (!$principalCollection instanceof IPrincipalCollection) { + // Not a principal collection, we're simply going to ignore + // this. + continue; + } + + $result = $principalCollection->findByUri($uri); + if ($result) { + return $result; + } + + } + + } + + /** + * Principal property search + * + * This method can search for principals matching certain values in + * properties. + * + * This method will return a list of properties for the matched properties. + * + * @param array $searchProperties The properties to search on. This is a + * key-value list. The keys are property + * names, and the values the strings to + * match them on. + * @param array $requestedProperties This is the list of properties to + * return for every match. + * @param string $collectionUri The principal collection to search on. + * If this is ommitted, the standard + * principal collection-set will be used. + * @param string $test "allof" to use AND to search the + * properties. 'anyof' for OR. + * @return array This method returns an array structure similar to + * Sabre\DAV\Server::getPropertiesForPath. Returned + * properties are index by a HTTP status code. + */ + function principalSearch(array $searchProperties, array $requestedProperties, $collectionUri = null, $test = 'allof') { + + if (!is_null($collectionUri)) { + $uris = [$collectionUri]; + } else { + $uris = $this->principalCollectionSet; + } + + $lookupResults = []; + foreach ($uris as $uri) { + + $principalCollection = $this->server->tree->getNodeForPath($uri); + if (!$principalCollection instanceof IPrincipalCollection) { + // Not a principal collection, we're simply going to ignore + // this. + continue; + } + + $results = $principalCollection->searchPrincipals($searchProperties, $test); + foreach ($results as $result) { + $lookupResults[] = rtrim($uri, '/') . '/' . $result; + } + + } + + $matches = []; + + foreach ($lookupResults as $lookupResult) { + + list($matches[]) = $this->server->getPropertiesForPath($lookupResult, $requestedProperties, 0); + + } + + return $matches; + + } + + /** + * Sets up the plugin + * + * This method is automatically called by the server class. + * + * @param DAV\Server $server + * @return void + */ + function initialize(DAV\Server $server) { + + $this->server = $server; + $server->on('propFind', [$this, 'propFind'], 20); + $server->on('beforeMethod', [$this, 'beforeMethod'], 20); + $server->on('beforeBind', [$this, 'beforeBind'], 20); + $server->on('beforeUnbind', [$this, 'beforeUnbind'], 20); + $server->on('propPatch', [$this, 'propPatch']); + $server->on('beforeUnlock', [$this, 'beforeUnlock'], 20); + $server->on('report', [$this, 'report']); + $server->on('method:ACL', [$this, 'httpAcl']); + $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']); + + array_push($server->protectedProperties, + '{DAV:}alternate-URI-set', + '{DAV:}principal-URL', + '{DAV:}group-membership', + '{DAV:}principal-collection-set', + '{DAV:}current-user-principal', + '{DAV:}supported-privilege-set', + '{DAV:}current-user-privilege-set', + '{DAV:}acl', + '{DAV:}acl-restrictions', + '{DAV:}inherited-acl-set', + '{DAV:}owner', + '{DAV:}group' + ); + + // Automatically mapping nodes implementing IPrincipal to the + // {DAV:}principal resourcetype. + $server->resourceTypeMapping['Sabre\\DAVACL\\IPrincipal'] = '{DAV:}principal'; + + // Mapping the group-member-set property to the HrefList property + // class. + $server->xml->elementMap['{DAV:}group-member-set'] = 'Sabre\\DAV\\Xml\\Property\\Href'; + $server->xml->elementMap['{DAV:}acl'] = 'Sabre\\DAVACL\\Xml\\Property\\Acl'; + $server->xml->elementMap['{DAV:}expand-property'] = 'Sabre\\DAVACL\\Xml\\Request\\ExpandPropertyReport'; + $server->xml->elementMap['{DAV:}principal-property-search'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalPropertySearchReport'; + $server->xml->elementMap['{DAV:}principal-search-property-set'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalSearchPropertySetReport'; + + } + + /* {{{ Event handlers */ + + /** + * Triggered before any method is handled + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function beforeMethod(RequestInterface $request, ResponseInterface $response) { + + $method = $request->getMethod(); + $path = $request->getPath(); + + $exists = $this->server->tree->nodeExists($path); + + // If the node doesn't exists, none of these checks apply + if (!$exists) return; + + switch ($method) { + + case 'GET' : + case 'HEAD' : + case 'OPTIONS' : + // For these 3 we only need to know if the node is readable. + $this->checkPrivileges($path, '{DAV:}read'); + break; + + case 'PUT' : + case 'LOCK' : + case 'UNLOCK' : + // This method requires the write-content priv if the node + // already exists, and bind on the parent if the node is being + // created. + // The bind privilege is handled in the beforeBind event. + $this->checkPrivileges($path, '{DAV:}write-content'); + break; + + + case 'PROPPATCH' : + $this->checkPrivileges($path, '{DAV:}write-properties'); + break; + + case 'ACL' : + $this->checkPrivileges($path, '{DAV:}write-acl'); + break; + + case 'COPY' : + case 'MOVE' : + // Copy requires read privileges on the entire source tree. + // If the target exists write-content normally needs to be + // checked, however, we're deleting the node beforehand and + // creating a new one after, so this is handled by the + // beforeUnbind event. + // + // The creation of the new node is handled by the beforeBind + // event. + // + // If MOVE is used beforeUnbind will also be used to check if + // the sourcenode can be deleted. + $this->checkPrivileges($path, '{DAV:}read', self::R_RECURSIVE); + + break; + + } + + } + + /** + * Triggered before a new node is created. + * + * This allows us to check permissions for any operation that creates a + * new node, such as PUT, MKCOL, MKCALENDAR, LOCK, COPY and MOVE. + * + * @param string $uri + * @return void + */ + function beforeBind($uri) { + + list($parentUri) = Uri\split($uri); + $this->checkPrivileges($parentUri, '{DAV:}bind'); + + } + + /** + * Triggered before a node is deleted + * + * This allows us to check permissions for any operation that will delete + * an existing node. + * + * @param string $uri + * @return void + */ + function beforeUnbind($uri) { + + list($parentUri) = Uri\split($uri); + $this->checkPrivileges($parentUri, '{DAV:}unbind', self::R_RECURSIVEPARENTS); + + } + + /** + * Triggered before a node is unlocked. + * + * @param string $uri + * @param DAV\Locks\LockInfo $lock + * @TODO: not yet implemented + * @return void + */ + function beforeUnlock($uri, DAV\Locks\LockInfo $lock) { + + + } + + /** + * Triggered before properties are looked up in specific nodes. + * + * @param DAV\PropFind $propFind + * @param DAV\INode $node + * @param array $requestedProperties + * @param array $returnedProperties + * @TODO really should be broken into multiple methods, or even a class. + * @return bool + */ + function propFind(DAV\PropFind $propFind, DAV\INode $node) { + + $path = $propFind->getPath(); + + // Checking the read permission + if (!$this->checkPrivileges($path, '{DAV:}read', self::R_PARENT, false)) { + // User is not allowed to read properties + + // Returning false causes the property-fetching system to pretend + // that the node does not exist, and will cause it to be hidden + // from listings such as PROPFIND or the browser plugin. + if ($this->hideNodesFromListings) { + return false; + } + + // Otherwise we simply mark every property as 403. + foreach ($propFind->getRequestedProperties() as $requestedProperty) { + $propFind->set($requestedProperty, null, 403); + } + + return; + + } + + /* Adding principal properties */ + if ($node instanceof IPrincipal) { + + $propFind->handle('{DAV:}alternate-URI-set', function() use ($node) { + return new DAV\Xml\Property\Href($node->getAlternateUriSet()); + }); + $propFind->handle('{DAV:}principal-URL', function() use ($node) { + return new DAV\Xml\Property\Href($node->getPrincipalUrl() . '/'); + }); + $propFind->handle('{DAV:}group-member-set', function() use ($node) { + $members = $node->getGroupMemberSet(); + foreach ($members as $k => $member) { + $members[$k] = rtrim($member, '/') . '/'; + } + return new DAV\Xml\Property\Href($members); + }); + $propFind->handle('{DAV:}group-membership', function() use ($node) { + $members = $node->getGroupMembership(); + foreach ($members as $k => $member) { + $members[$k] = rtrim($member, '/') . '/'; + } + return new DAV\Xml\Property\Href($members); + }); + $propFind->handle('{DAV:}displayname', [$node, 'getDisplayName']); + + } + + $propFind->handle('{DAV:}principal-collection-set', function() { + + $val = $this->principalCollectionSet; + // Ensuring all collections end with a slash + foreach ($val as $k => $v) $val[$k] = $v . '/'; + return new DAV\Xml\Property\Href($val); + + }); + $propFind->handle('{DAV:}current-user-principal', function() { + if ($url = $this->getCurrentUserPrincipal()) { + return new Xml\Property\Principal(Xml\Property\Principal::HREF, $url . '/'); + } else { + return new Xml\Property\Principal(Xml\Property\Principal::UNAUTHENTICATED); + } + }); + $propFind->handle('{DAV:}supported-privilege-set', function() use ($node) { + return new Xml\Property\SupportedPrivilegeSet($this->getSupportedPrivilegeSet($node)); + }); + $propFind->handle('{DAV:}current-user-privilege-set', function() use ($node, $propFind, $path) { + if (!$this->checkPrivileges($path, '{DAV:}read-current-user-privilege-set', self::R_PARENT, false)) { + $propFind->set('{DAV:}current-user-privilege-set', null, 403); + } else { + $val = $this->getCurrentUserPrivilegeSet($node); + if (!is_null($val)) { + return new Xml\Property\CurrentUserPrivilegeSet($val); + } + } + }); + $propFind->handle('{DAV:}acl', function() use ($node, $propFind, $path) { + /* The ACL property contains all the permissions */ + if (!$this->checkPrivileges($path, '{DAV:}read-acl', self::R_PARENT, false)) { + $propFind->set('{DAV:}acl', null, 403); + } else { + $acl = $this->getACL($node); + if (!is_null($acl)) { + return new Xml\Property\Acl($this->getACL($node)); + } + } + }); + $propFind->handle('{DAV:}acl-restrictions', function() { + return new Xml\Property\AclRestrictions(); + }); + + /* Adding ACL properties */ + if ($node instanceof IACL) { + $propFind->handle('{DAV:}owner', function() use ($node) { + return new DAV\Xml\Property\Href($node->getOwner() . '/'); + }); + } + + } + + /** + * This method intercepts PROPPATCH methods and make sure the + * group-member-set is updated correctly. + * + * @param string $path + * @param DAV\PropPatch $propPatch + * @return void + */ + function propPatch($path, DAV\PropPatch $propPatch) { + + $propPatch->handle('{DAV:}group-member-set', function($value) use ($path) { + if (is_null($value)) { + $memberSet = []; + } elseif ($value instanceof DAV\Xml\Property\Href) { + $memberSet = array_map( + [$this->server, 'calculateUri'], + $value->getHrefs() + ); + } else { + throw new DAV\Exception('The group-member-set property MUST be an instance of Sabre\DAV\Property\HrefList or null'); + } + $node = $this->server->tree->getNodeForPath($path); + if (!($node instanceof IPrincipal)) { + // Fail + return false; + } + + $node->setGroupMemberSet($memberSet); + // We must also clear our cache, just in case + + $this->principalMembershipCache = []; + + return true; + }); + + } + + /** + * This method handles HTTP REPORT requests + * + * @param string $reportName + * @param mixed $report + * @param mixed $path + * @return bool + */ + function report($reportName, $report, $path) { + + switch ($reportName) { + + case '{DAV:}principal-property-search' : + $this->server->transactionType = 'report-principal-property-search'; + $this->principalPropertySearchReport($report); + return false; + case '{DAV:}principal-search-property-set' : + $this->server->transactionType = 'report-principal-search-property-set'; + $this->principalSearchPropertySetReport($report); + return false; + case '{DAV:}expand-property' : + $this->server->transactionType = 'report-expand-property'; + $this->expandPropertyReport($report); + return false; + + } + + } + + /** + * This method is responsible for handling the 'ACL' event. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpAcl(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + $body = $request->getBodyAsString(); + + if (!$body) { + throw new DAV\Exception\BadRequest('XML body expected in ACL request'); + } + + $acl = $this->server->xml->expect('{DAV:}acl', $body); + $newAcl = $acl->getPrivileges(); + + // Normalizing urls + foreach ($newAcl as $k => $newAce) { + $newAcl[$k]['principal'] = $this->server->calculateUri($newAce['principal']); + } + $node = $this->server->tree->getNodeForPath($path); + + if (!$node instanceof IACL) { + throw new DAV\Exception\MethodNotAllowed('This node does not support the ACL method'); + } + + $oldAcl = $this->getACL($node); + + $supportedPrivileges = $this->getFlatPrivilegeSet($node); + + /* Checking if protected principals from the existing principal set are + not overwritten. */ + foreach ($oldAcl as $oldAce) { + + if (!isset($oldAce['protected']) || !$oldAce['protected']) continue; + + $found = false; + foreach ($newAcl as $newAce) { + if ( + $newAce['privilege'] === $oldAce['privilege'] && + $newAce['principal'] === $oldAce['principal'] && + $newAce['protected'] + ) + $found = true; + } + + if (!$found) + throw new Exception\AceConflict('This resource contained a protected {DAV:}ace, but this privilege did not occur in the ACL request'); + + } + + foreach ($newAcl as $newAce) { + + // Do we recognize the privilege + if (!isset($supportedPrivileges[$newAce['privilege']])) { + throw new Exception\NotSupportedPrivilege('The privilege you specified (' . $newAce['privilege'] . ') is not recognized by this server'); + } + + if ($supportedPrivileges[$newAce['privilege']]['abstract']) { + throw new Exception\NoAbstract('The privilege you specified (' . $newAce['privilege'] . ') is an abstract privilege'); + } + + // Looking up the principal + try { + $principal = $this->server->tree->getNodeForPath($newAce['principal']); + } catch (DAV\Exception\NotFound $e) { + throw new Exception\NotRecognizedPrincipal('The specified principal (' . $newAce['principal'] . ') does not exist'); + } + if (!($principal instanceof IPrincipal)) { + throw new Exception\NotRecognizedPrincipal('The specified uri (' . $newAce['principal'] . ') is not a principal'); + } + + } + $node->setACL($newAcl); + + $response->setStatus(200); + + // Breaking the event chain, because we handled this method. + return false; + + } + + /* }}} */ + + /* Reports {{{ */ + + /** + * The expand-property report is defined in RFC3253 section 3-8. + * + * This report is very similar to a standard PROPFIND. The difference is + * that it has the additional ability to look at properties containing a + * {DAV:}href element, follow that property and grab additional elements + * there. + * + * Other rfc's, such as ACL rely on this report, so it made sense to put + * it in this plugin. + * + * @param Xml\Request\ExpandPropertyReport $report + * @return void + */ + protected function expandPropertyReport($report) { + + $depth = $this->server->getHTTPDepth(0); + $requestUri = $this->server->getRequestUri(); + + $result = $this->expandProperties($requestUri, $report->properties, $depth); + + $xml = $this->server->xml->write( + '{DAV:}multistatus', + new DAV\Xml\Response\MultiStatus($result), + $this->server->getBaseUri() + ); + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setStatus(207); + $this->server->httpResponse->setBody($xml); + + } + + /** + * This method expands all the properties and returns + * a list with property values + * + * @param array $path + * @param array $requestedProperties the list of required properties + * @param int $depth + * @return array + */ + protected function expandProperties($path, array $requestedProperties, $depth) { + + $foundProperties = $this->server->getPropertiesForPath($path, array_keys($requestedProperties), $depth); + + $result = []; + + foreach ($foundProperties as $node) { + + foreach ($requestedProperties as $propertyName => $childRequestedProperties) { + + // We're only traversing if sub-properties were requested + if (count($childRequestedProperties) === 0) continue; + + // We only have to do the expansion if the property was found + // and it contains an href element. + if (!array_key_exists($propertyName, $node[200])) continue; + + if (!$node[200][$propertyName] instanceof DAV\Xml\Property\Href) { + continue; + } + + $childHrefs = $node[200][$propertyName]->getHrefs(); + $childProps = []; + + foreach ($childHrefs as $href) { + // Gathering the result of the children + $childProps[] = [ + 'name' => '{DAV:}response', + 'value' => $this->expandProperties($href, $childRequestedProperties, 0)[0] + ]; + } + + // Replacing the property with its expannded form. + $node[200][$propertyName] = $childProps; + + } + $result[] = new DAV\Xml\Element\Response($node['href'], $node); + + } + + return $result; + + } + + /** + * principalSearchPropertySetReport + * + * This method responsible for handing the + * {DAV:}principal-search-property-set report. This report returns a list + * of properties the client may search on, using the + * {DAV:}principal-property-search report. + * + * @param Xml\Request\PrincipalSearchPropertySetReport $report + * @return void + */ + protected function principalSearchPropertySetReport($report) { + + $httpDepth = $this->server->getHTTPDepth(0); + if ($httpDepth !== 0) { + throw new DAV\Exception\BadRequest('This report is only defined when Depth: 0'); + } + + $writer = $this->server->xml->getWriter(); + $writer->openMemory(); + $writer->startDocument(); + + $writer->startElement('{DAV:}principal-search-property-set'); + + foreach ($this->principalSearchPropertySet as $propertyName => $description) { + + $writer->startElement('{DAV:}principal-search-property'); + $writer->startElement('{DAV:}prop'); + + $writer->writeElement($propertyName); + + $writer->endElement(); // prop + + if ($description) { + $writer->write([[ + 'name' => '{DAV:}description', + 'value' => $description, + 'attributes' => ['xml:lang' => 'en'] + ]]); + } + + $writer->endElement(); // principal-search-property + + + } + + $writer->endElement(); // principal-search-property-set + + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setStatus(200); + $this->server->httpResponse->setBody($writer->outputMemory()); + + } + + /** + * principalPropertySearchReport + * + * This method is responsible for handing the + * {DAV:}principal-property-search report. This report can be used for + * clients to search for groups of principals, based on the value of one + * or more properties. + * + * @param Xml\Request\PrincipalPropertySearchReport $report + * @return void + */ + protected function principalPropertySearchReport($report) { + + $uri = null; + if (!$report->applyToPrincipalCollectionSet) { + $uri = $this->server->httpRequest->getPath(); + } + if ($this->server->getHttpDepth('0') !== 0) { + throw new BadRequest('Depth must be 0'); + } + $result = $this->principalSearch( + $report->searchProperties, + $report->properties, + $uri, + $report->test + ); + + $prefer = $this->server->getHTTPPrefer(); + + $this->server->httpResponse->setStatus(207); + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer'); + $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return'] === 'minimal')); + + } + + /* }}} */ + + /** + * This method is used to generate HTML output for the + * DAV\Browser\Plugin. This allows us to generate an interface users + * can use to create new calendars. + * + * @param DAV\INode $node + * @param string $output + * @return bool + */ + function htmlActionsPanel(DAV\INode $node, &$output) { + + if (!$node instanceof PrincipalCollection) + return; + + $output .= '
+

Create new principal

+ + +
+
+
+ +
+ '; + + return false; + + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'Adds support for WebDAV ACL (rfc3744)', + 'link' => 'http://sabre.io/dav/acl/', + ]; + + } +} diff --git a/vendor/sabre/dav/lib/DAVACL/Principal.php b/vendor/sabre/dav/lib/DAVACL/Principal.php new file mode 100644 index 000000000..16375d3fc --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/Principal.php @@ -0,0 +1,288 @@ +principalBackend = $principalBackend; + $this->principalProperties = $principalProperties; + + } + + /** + * Returns the full principal url + * + * @return string + */ + function getPrincipalUrl() { + + return $this->principalProperties['uri']; + + } + + /** + * Returns a list of alternative urls for a principal + * + * This can for example be an email address, or ldap url. + * + * @return array + */ + function getAlternateUriSet() { + + $uris = []; + if (isset($this->principalProperties['{DAV:}alternate-URI-set'])) { + + $uris = $this->principalProperties['{DAV:}alternate-URI-set']; + + } + + if (isset($this->principalProperties['{http://sabredav.org/ns}email-address'])) { + $uris[] = 'mailto:' . $this->principalProperties['{http://sabredav.org/ns}email-address']; + } + + return array_unique($uris); + + } + + /** + * Returns the list of group members + * + * If this principal is a group, this function should return + * all member principal uri's for the group. + * + * @return array + */ + function getGroupMemberSet() { + + return $this->principalBackend->getGroupMemberSet($this->principalProperties['uri']); + + } + + /** + * Returns the list of groups this principal is member of + * + * If this principal is a member of a (list of) groups, this function + * should return a list of principal uri's for it's members. + * + * @return array + */ + function getGroupMembership() { + + return $this->principalBackend->getGroupMemberShip($this->principalProperties['uri']); + + } + + /** + * Sets a list of group members + * + * If this principal is a group, this method sets all the group members. + * The list of members is always overwritten, never appended to. + * + * This method should throw an exception if the members could not be set. + * + * @param array $groupMembers + * @return void + */ + function setGroupMemberSet(array $groupMembers) { + + $this->principalBackend->setGroupMemberSet($this->principalProperties['uri'], $groupMembers); + + } + + /** + * Returns this principals name. + * + * @return string + */ + function getName() { + + $uri = $this->principalProperties['uri']; + list(, $name) = URLUtil::splitPath($uri); + return $name; + + } + + /** + * Returns the name of the user + * + * @return string + */ + function getDisplayName() { + + if (isset($this->principalProperties['{DAV:}displayname'])) { + return $this->principalProperties['{DAV:}displayname']; + } else { + return $this->getName(); + } + + } + + /** + * Returns a list of properties + * + * @param array $requestedProperties + * @return array + */ + function getProperties($requestedProperties) { + + $newProperties = []; + foreach ($requestedProperties as $propName) { + + if (isset($this->principalProperties[$propName])) { + $newProperties[$propName] = $this->principalProperties[$propName]; + } + + } + + return $newProperties; + + } + + /** + * Updates properties on this node. + * + * This method received a PropPatch object, which contains all the + * information about the update. + * + * To update specific properties, call the 'handle' method on this object. + * Read the PropPatch documentation for more information. + * + * @param DAV\PropPatch $propPatch + * @return void + */ + function propPatch(DAV\PropPatch $propPatch) { + + return $this->principalBackend->updatePrincipal( + $this->principalProperties['uri'], + $propPatch + ); + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + + return $this->principalProperties['uri']; + + + } + + /** + * Returns a group principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getGroup() { + + return null; + + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + + return [ + [ + 'privilege' => '{DAV:}read', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ], + ]; + + } + + /** + * Updates the ACL + * + * This method will receive a list of new ACE's. + * + * @param array $acl + * @return void + */ + function setACL(array $acl) { + + throw new DAV\Exception\MethodNotAllowed('Updating ACLs is not allowed here'); + + } + + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + function getSupportedPrivilegeSet() { + + return null; + + } + +} diff --git a/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/AbstractBackend.php b/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/AbstractBackend.php new file mode 100644 index 000000000..9bf9ba445 --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/AbstractBackend.php @@ -0,0 +1,53 @@ +searchPrincipals( + $principalPrefix, + ['{http://sabredav.org/ns}email-address' => substr($uri, 7)] + ); + + if ($result) { + return $result[0]; + } + + } + +} diff --git a/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/BackendInterface.php b/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/BackendInterface.php new file mode 100644 index 000000000..2cb83071a --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/BackendInterface.php @@ -0,0 +1,141 @@ + [ + 'dbField' => 'displayname', + ], + + /** + * This is the users' primary email-address. + */ + '{http://sabredav.org/ns}email-address' => [ + 'dbField' => 'email', + ], + ]; + + /** + * Sets up the backend. + * + * @param PDO $pdo + */ + function __construct(\PDO $pdo) { + + $this->pdo = $pdo; + + } + + /** + * Returns a list of principals based on a prefix. + * + * This prefix will often contain something like 'principals'. You are only + * expected to return principals that are in this base path. + * + * You are expected to return at least a 'uri' for every user, you can + * return any additional properties if you wish so. Common properties are: + * {DAV:}displayname + * {http://sabredav.org/ns}email-address - This is a custom SabreDAV + * field that's actualy injected in a number of other properties. If + * you have an email address, use this property. + * + * @param string $prefixPath + * @return array + */ + function getPrincipalsByPrefix($prefixPath) { + + $fields = [ + 'uri', + ]; + + foreach ($this->fieldMap as $key => $value) { + $fields[] = $value['dbField']; + } + $result = $this->pdo->query('SELECT ' . implode(',', $fields) . ' FROM ' . $this->tableName); + + $principals = []; + + while ($row = $result->fetch(\PDO::FETCH_ASSOC)) { + + // Checking if the principal is in the prefix + list($rowPrefix) = URLUtil::splitPath($row['uri']); + if ($rowPrefix !== $prefixPath) continue; + + $principal = [ + 'uri' => $row['uri'], + ]; + foreach ($this->fieldMap as $key => $value) { + if ($row[$value['dbField']]) { + $principal[$key] = $row[$value['dbField']]; + } + } + $principals[] = $principal; + + } + + return $principals; + + } + + /** + * Returns a specific principal, specified by it's path. + * The returned structure should be the exact same as from + * getPrincipalsByPrefix. + * + * @param string $path + * @return array + */ + function getPrincipalByPath($path) { + + $fields = [ + 'id', + 'uri', + ]; + + foreach ($this->fieldMap as $key => $value) { + $fields[] = $value['dbField']; + } + $stmt = $this->pdo->prepare('SELECT ' . implode(',', $fields) . ' FROM ' . $this->tableName . ' WHERE uri = ?'); + $stmt->execute([$path]); + + $row = $stmt->fetch(\PDO::FETCH_ASSOC); + if (!$row) return; + + $principal = [ + 'id' => $row['id'], + 'uri' => $row['uri'], + ]; + foreach ($this->fieldMap as $key => $value) { + if ($row[$value['dbField']]) { + $principal[$key] = $row[$value['dbField']]; + } + } + return $principal; + + } + + /** + * Updates one ore more webdav properties on a principal. + * + * The list of mutations is stored in a Sabre\DAV\PropPatch object. + * To do the actual updates, you must tell this object which properties + * you're going to process with the handle() method. + * + * Calling the handle method is like telling the PropPatch object "I + * promise I can handle updating this property". + * + * Read the PropPatch documenation for more info and examples. + * + * @param string $path + * @param DAV\PropPatch $propPatch + */ + function updatePrincipal($path, DAV\PropPatch $propPatch) { + + $propPatch->handle(array_keys($this->fieldMap), function($properties) use ($path) { + + $query = "UPDATE " . $this->tableName . " SET "; + $first = true; + + $values = []; + + foreach ($properties as $key => $value) { + + $dbField = $this->fieldMap[$key]['dbField']; + + if (!$first) { + $query .= ', '; + } + $first = false; + $query .= $dbField . ' = :' . $dbField; + $values[$dbField] = $value; + + } + + $query .= " WHERE uri = :uri"; + $values['uri'] = $path; + + $stmt = $this->pdo->prepare($query); + $stmt->execute($values); + + return true; + + }); + + } + + /** + * This method is used to search for principals matching a set of + * properties. + * + * This search is specifically used by RFC3744's principal-property-search + * REPORT. + * + * The actual search should be a unicode-non-case-sensitive search. The + * keys in searchProperties are the WebDAV property names, while the values + * are the property values to search on. + * + * By default, if multiple properties are submitted to this method, the + * various properties should be combined with 'AND'. If $test is set to + * 'anyof', it should be combined using 'OR'. + * + * This method should simply return an array with full principal uri's. + * + * If somebody attempted to search on a property the backend does not + * support, you should simply return 0 results. + * + * You can also just return 0 results if you choose to not support + * searching at all, but keep in mind that this may stop certain features + * from working. + * + * @param string $prefixPath + * @param array $searchProperties + * @param string $test + * @return array + */ + function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') { + if (count($searchProperties) == 0) return []; //No criteria + + $query = 'SELECT uri FROM ' . $this->tableName . ' WHERE '; + $values = []; + foreach ($searchProperties as $property => $value) { + switch ($property) { + case '{DAV:}displayname' : + $column = "displayname"; + break; + case '{http://sabredav.org/ns}email-address' : + $column = "email"; + break; + default : + // Unsupported property + return []; + } + if (count($values) > 0) $query .= (strcmp($test, "anyof") == 0 ? " OR " : " AND "); + $query .= 'lower(' . $column . ') LIKE lower(?)'; + $values[] = '%' . $value . '%'; + + } + $stmt = $this->pdo->prepare($query); + $stmt->execute($values); + + $principals = []; + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + + // Checking if the principal is in the prefix + list($rowPrefix) = URLUtil::splitPath($row['uri']); + if ($rowPrefix !== $prefixPath) continue; + + $principals[] = $row['uri']; + + } + + return $principals; + + } + + /** + * Finds a principal by its URI. + * + * This method may receive any type of uri, but mailto: addresses will be + * the most common. + * + * Implementation of this API is optional. It is currently used by the + * CalDAV system to find principals based on their email addresses. If this + * API is not implemented, some features may not work correctly. + * + * This method must return a relative principal path, or null, if the + * principal was not found or you refuse to find it. + * + * @param string $uri + * @param string $principalPrefix + * @return string + */ + function findByUri($uri, $principalPrefix) { + $value = null; + $scheme = null; + list($scheme, $value) = explode(":", $uri, 2); + if ($value == null) return null; + + $uri = null; + switch ($scheme){ + case "mailto": + $query = 'SELECT uri FROM ' . $this->tableName . ' WHERE lower(email)=lower(?)'; + $stmt = $this->pdo->prepare($query); + $stmt->execute([ $value ]); + + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + // Checking if the principal is in the prefix + list($rowPrefix) = URLUtil::splitPath($row['uri']); + if ($rowPrefix !== $principalPrefix) continue; + + $uri = $row['uri']; + break; //Stop on first match + } + break; + default: + //unsupported uri scheme + return null; + } + return $uri; + } + + /** + * Returns the list of members for a group-principal + * + * @param string $principal + * @return array + */ + function getGroupMemberSet($principal) { + + $principal = $this->getPrincipalByPath($principal); + if (!$principal) throw new DAV\Exception('Principal not found'); + + $stmt = $this->pdo->prepare('SELECT principals.uri as uri FROM ' . $this->groupMembersTableName . ' AS groupmembers LEFT JOIN ' . $this->tableName . ' AS principals ON groupmembers.member_id = principals.id WHERE groupmembers.principal_id = ?'); + $stmt->execute([$principal['id']]); + + $result = []; + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + $result[] = $row['uri']; + } + return $result; + + } + + /** + * Returns the list of groups a principal is a member of + * + * @param string $principal + * @return array + */ + function getGroupMembership($principal) { + + $principal = $this->getPrincipalByPath($principal); + if (!$principal) throw new DAV\Exception('Principal not found'); + + $stmt = $this->pdo->prepare('SELECT principals.uri as uri FROM ' . $this->groupMembersTableName . ' AS groupmembers LEFT JOIN ' . $this->tableName . ' AS principals ON groupmembers.principal_id = principals.id WHERE groupmembers.member_id = ?'); + $stmt->execute([$principal['id']]); + + $result = []; + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + $result[] = $row['uri']; + } + return $result; + + } + + /** + * Updates the list of group members for a group principal. + * + * The principals should be passed as a list of uri's. + * + * @param string $principal + * @param array $members + * @return void + */ + function setGroupMemberSet($principal, array $members) { + + // Grabbing the list of principal id's. + $stmt = $this->pdo->prepare('SELECT id, uri FROM ' . $this->tableName . ' WHERE uri IN (? ' . str_repeat(', ? ', count($members)) . ');'); + $stmt->execute(array_merge([$principal], $members)); + + $memberIds = []; + $principalId = null; + + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + if ($row['uri'] == $principal) { + $principalId = $row['id']; + } else { + $memberIds[] = $row['id']; + } + } + if (!$principalId) throw new DAV\Exception('Principal not found'); + + // Wiping out old members + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->groupMembersTableName . ' WHERE principal_id = ?;'); + $stmt->execute([$principalId]); + + foreach ($memberIds as $memberId) { + + $stmt = $this->pdo->prepare('INSERT INTO ' . $this->groupMembersTableName . ' (principal_id, member_id) VALUES (?, ?);'); + $stmt->execute([$principalId, $memberId]); + + } + + } + + /** + * Creates a new principal. + * + * This method receives a full path for the new principal. The mkCol object + * contains any additional webdav properties specified during the creation + * of the principal. + * + * @param string $path + * @param MkCol $mkCol + * @return void + */ + function createPrincipal($path, MkCol $mkCol) { + + $stmt = $this->pdo->prepare('INSERT INTO ' . $this->tableName . ' (uri) VALUES (?)'); + $stmt->execute([$path]); + $this->updatePrincipal($path, $mkCol); + + } + +} diff --git a/vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php b/vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php new file mode 100644 index 000000000..54911e7b5 --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php @@ -0,0 +1,151 @@ +principalBackend, $principal); + + } + + /** + * Creates a new collection. + * + * This method will receive a MkCol object with all the information about + * the new collection that's being created. + * + * The MkCol object contains information about the resourceType of the new + * collection. If you don't support the specified resourceType, you should + * throw Exception\InvalidResourceType. + * + * The object also contains a list of WebDAV properties for the new + * collection. + * + * You should call the handle() method on this object to specify exactly + * which properties you are storing. This allows the system to figure out + * exactly which properties you didn't store, which in turn allows other + * plugins (such as the propertystorage plugin) to handle storing the + * property for you. + * + * @param string $name + * @param MkCol $mkCol + * @throws Exception\InvalidResourceType + * @return void + */ + function createExtendedCollection($name, MkCol $mkCol) { + + if (!$mkCol->hasResourceType('{DAV:}principal')) { + throw new InvalidResourceType('Only resources of type {DAV:}principal may be created here'); + } + + $this->principalBackend->createPrincipal( + $this->principalPrefix . '/' . $name, + $mkCol + ); + + } + + /** + * Returns the owner principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getOwner() { + return null; + } + + /** + * Returns a group principal + * + * This must be a url to a principal, or null if there's no owner + * + * @return string|null + */ + function getGroup() { + return null; + } + + /** + * Returns a list of ACE's for this node. + * + * Each ACE has the following properties: + * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are + * currently the only supported privileges + * * 'principal', a url to the principal who owns the node + * * 'protected' (optional), indicating that this ACE is not allowed to + * be updated. + * + * @return array + */ + function getACL() { + return [ + [ + 'principal' => '{DAV:}authenticated', + 'privilege' => '{DAV:}read', + 'protected' => true, + ], + ]; + } + + /** + * Updates the ACL + * + * This method will receive a list of new ACE's as an array argument. + * + * @param array $acl + * @return void + */ + function setACL(array $acl) { + + throw new Forbidden('Updating ACLs is not allowed on this node'); + + } + + /** + * Returns the list of supported privileges for this node. + * + * The returned data structure is a list of nested privileges. + * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple + * standard structure. + * + * If null is returned from this method, the default privilege set is used, + * which is fine for most common usecases. + * + * @return array|null + */ + function getSupportedPrivilegeSet() { + + return null; + + } + +} diff --git a/vendor/sabre/dav/lib/DAVACL/Xml/Property/Acl.php b/vendor/sabre/dav/lib/DAVACL/Xml/Property/Acl.php new file mode 100644 index 000000000..9f5e40df1 --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/Xml/Property/Acl.php @@ -0,0 +1,277 @@ +privileges = $privileges; + $this->prefixBaseUrl = $prefixBaseUrl; + + } + + /** + * Returns the list of privileges for this property + * + * @return array + */ + function getPrivileges() { + + return $this->privileges; + + } + + /** + * The xmlSerialize metod is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->privileges as $ace) { + + $this->serializeAce($writer, $ace); + + } + + } + + /** + * Generate html representation for this value. + * + * The html output is 100% trusted, and no effort is being made to sanitize + * it. It's up to the implementor to sanitize user provided values. + * + * The output must be in UTF-8. + * + * The baseUri parameter is a url to the root of the application, and can + * be used to construct local links. + * + * @param HtmlOutputHelper $html + * @return string + */ + function toHtml(HtmlOutputHelper $html) { + + ob_start(); + echo ""; + echo ""; + foreach ($this->privileges as $privilege) { + + echo ''; + // if it starts with a {, it's a special principal + if ($privilege['principal'][0] === '{') { + echo ''; + } else { + echo ''; + } + echo ''; + echo ''; + echo ''; + + } + echo "
PrincipalPrivilege
', $html->xmlName($privilege['principal']), '', $html->link($privilege['principal']), '', $html->xmlName($privilege['privilege']), ''; + if (!empty($privilege['protected'])) echo '(protected)'; + echo '
"; + return ob_get_clean(); + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * Important note 2: You are responsible for advancing the reader to the + * next element. Not doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elementMap = [ + '{DAV:}ace' => 'Sabre\Xml\Element\KeyValue', + '{DAV:}privilege' => 'Sabre\Xml\Element\Elements', + '{DAV:}principal' => 'Sabre\DAVACL\Xml\Property\Principal', + ]; + + $privileges = []; + + foreach ((array)$reader->parseInnerTree($elementMap) as $element) { + + if ($element['name'] !== '{DAV:}ace') { + continue; + } + $ace = $element['value']; + + if (empty($ace['{DAV:}principal'])) { + throw new DAV\Exception\BadRequest('Each {DAV:}ace element must have one {DAV:}principal element'); + } + $principal = $ace['{DAV:}principal']; + + switch ($principal->getType()) { + case Principal::HREF : + $principal = $principal->getHref(); + break; + case Principal::AUTHENTICATED : + $principal = '{DAV:}authenticated'; + break; + case Principal::UNAUTHENTICATED : + $principal = '{DAV:}unauthenticated'; + break; + case Principal::ALL : + $principal = '{DAV:}all'; + break; + + } + + $protected = array_key_exists('{DAV:}protected', $ace); + + if (!isset($ace['{DAV:}grant'])) { + throw new DAV\Exception\NotImplemented('Every {DAV:}ace element must have a {DAV:}grant element. {DAV:}deny is not yet supported'); + } + foreach ($ace['{DAV:}grant'] as $elem) { + if ($elem['name'] !== '{DAV:}privilege') { + continue; + } + + foreach ($elem['value'] as $priv) { + $privileges[] = [ + 'principal' => $principal, + 'protected' => $protected, + 'privilege' => $priv, + ]; + } + + } + + } + + return new self($privileges); + + } + + /** + * Serializes a single access control entry. + * + * @param Writer $writer + * @param array $ace + * @return void + */ + private function serializeAce(Writer $writer, array $ace) { + + $writer->startElement('{DAV:}ace'); + + switch ($ace['principal']) { + case '{DAV:}authenticated' : + $principal = new Principal(Principal::AUTHENTICATED); + break; + case '{DAV:}unauthenticated' : + $principal = new Principal(Principal::UNAUTHENTICATED); + break; + case '{DAV:}all' : + $principal = new Principal(Principal::ALL); + break; + default: + $principal = new Principal(Principal::HREF, $ace['principal']); + break; + } + + $writer->writeElement('{DAV:}principal', $principal); + $writer->startElement('{DAV:}grant'); + $writer->startElement('{DAV:}privilege'); + + $writer->writeElement($ace['privilege']); + + $writer->endElement(); // privilege + $writer->endElement(); // grant + + if (!empty($ace['protected'])) { + $writer->writeElement('{DAV:}protected'); + } + + $writer->endElement(); // ace + + } + +} diff --git a/vendor/sabre/dav/lib/DAVACL/Xml/Property/AclRestrictions.php b/vendor/sabre/dav/lib/DAVACL/Xml/Property/AclRestrictions.php new file mode 100644 index 000000000..f669cc5e1 --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/Xml/Property/AclRestrictions.php @@ -0,0 +1,45 @@ +writeElement('{DAV:}grant-only'); + $writer->writeElement('{DAV:}no-invert'); + + } + +} diff --git a/vendor/sabre/dav/lib/DAVACL/Xml/Property/CurrentUserPrivilegeSet.php b/vendor/sabre/dav/lib/DAVACL/Xml/Property/CurrentUserPrivilegeSet.php new file mode 100644 index 000000000..0a95eb2b7 --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/Xml/Property/CurrentUserPrivilegeSet.php @@ -0,0 +1,159 @@ +privileges = $privileges; + + } + + /** + * The xmlSerialize metod is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->privileges as $privName) { + + $writer->startElement('{DAV:}privilege'); + $writer->writeElement($privName); + $writer->endElement(); + + } + + + } + + /** + * Returns true or false, whether the specified principal appears in the + * list. + * + * @param string $privilegeName + * @return bool + */ + function has($privilegeName) { + + return in_array($privilegeName, $this->privileges); + + } + + /** + * Returns the list of privileges. + * + * @return array + */ + function getValue() { + + return $this->privileges; + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $result = []; + + $tree = $reader->parseInnerTree(['{DAV:}privilege' => 'Sabre\\Xml\\Element\\Elements']); + foreach ($tree as $element) { + if ($element['name'] !== '{DAV:}privilege') { + continue; + } + $result[] = $element['value'][0]; + } + return new self($result); + + } + + /** + * Generate html representation for this value. + * + * The html output is 100% trusted, and no effort is being made to sanitize + * it. It's up to the implementor to sanitize user provided values. + * + * The output must be in UTF-8. + * + * The baseUri parameter is a url to the root of the application, and can + * be used to construct local links. + * + * @param HtmlOutputHelper $html + * @return string + */ + function toHtml(HtmlOutputHelper $html) { + + return implode( + ', ', + array_map([$html, 'xmlName'], $this->getValue()) + ); + + } + + +} diff --git a/vendor/sabre/dav/lib/DAVACL/Xml/Property/Principal.php b/vendor/sabre/dav/lib/DAVACL/Xml/Property/Principal.php new file mode 100644 index 000000000..d32249d8b --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/Xml/Property/Principal.php @@ -0,0 +1,196 @@ +type = $type; + if ($type === self::HREF && is_null($href)) { + throw new DAV\Exception('The href argument must be specified for the HREF principal type.'); + } + if ($href) { + $href = rtrim($href, '/') . '/'; + parent::__construct($href); + } + + } + + /** + * Returns the principal type + * + * @return int + */ + function getType() { + + return $this->type; + + } + + + /** + * The xmlSerialize metod is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + switch ($this->type) { + + case self::UNAUTHENTICATED : + $writer->writeElement('{DAV:}unauthenticated'); + break; + case self::AUTHENTICATED : + $writer->writeElement('{DAV:}authenticated'); + break; + case self::HREF : + parent::xmlSerialize($writer); + break; + case self::ALL : + $writer->writeElement('{DAV:}all'); + break; + } + + } + + /** + * Generate html representation for this value. + * + * The html output is 100% trusted, and no effort is being made to sanitize + * it. It's up to the implementor to sanitize user provided values. + * + * The output must be in UTF-8. + * + * The baseUri parameter is a url to the root of the application, and can + * be used to construct local links. + * + * @param HtmlOutputHelper $html + * @return string + */ + function toHtml(HtmlOutputHelper $html) { + + switch ($this->type) { + + case self::UNAUTHENTICATED : + return 'unauthenticated'; + case self::AUTHENTICATED : + return 'authenticated'; + case self::HREF : + return parent::toHtml($html); + case self::ALL : + return 'all'; + } + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called staticly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * Important note 2: You are responsible for advancing the reader to the + * next element. Not doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $tree = $reader->parseInnerTree()[0]; + + switch ($tree['name']) { + case '{DAV:}unauthenticated' : + return new self(self::UNAUTHENTICATED); + case '{DAV:}authenticated' : + return new self(self::AUTHENTICATED); + case '{DAV:}href': + return new self(self::HREF, $tree['value']); + case '{DAV:}all': + return new self(self::ALL); + default : + throw new BadRequest('Unknown or unsupported principal type: ' . $tree['name']); + } + + } + +} diff --git a/vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php b/vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php new file mode 100644 index 000000000..572bed4dd --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php @@ -0,0 +1,159 @@ +privileges = $privileges; + + } + + /** + * Returns the privilege value. + * + * @return array + */ + function getValue() { + + return $this->privileges; + + } + + /** + * The xmlSerialize metod is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + $this->serializePriv($writer, $this->privileges); + + } + + /** + * Generate html representation for this value. + * + * The html output is 100% trusted, and no effort is being made to sanitize + * it. It's up to the implementor to sanitize user provided values. + * + * The output must be in UTF-8. + * + * The baseUri parameter is a url to the root of the application, and can + * be used to construct local links. + * + * @param HtmlOutputHelper $html + * @return string + */ + function toHtml(HtmlOutputHelper $html) { + + $traverse = function($priv) use (&$traverse, $html) { + echo "
  • "; + echo $html->xmlName($priv['privilege']); + if (isset($priv['abstract']) && $priv['abstract']) { + echo " (abstract)"; + } + if (isset($priv['description'])) { + echo " " . $html->h($priv['description']); + } + if (isset($priv['aggregates'])) { + echo "\n
      \n"; + foreach ($priv['aggregates'] as $subPriv) { + $traverse($subPriv); + } + echo "
    "; + } + echo "
  • \n"; + }; + + ob_start(); + echo "
      "; + $traverse($this->getValue()); + echo "
    \n"; + + return ob_get_clean(); + + } + + + + /** + * Serializes a property + * + * This is a recursive function. + * + * @param Writer $writer + * @param array $privilege + * @return void + */ + private function serializePriv(Writer $writer, $privilege) { + + $writer->startElement('{DAV:}supported-privilege'); + + $writer->startElement('{DAV:}privilege'); + $writer->writeElement($privilege['privilege']); + $writer->endElement(); // privilege + + if (!empty($privilege['abstract'])) { + $writer->writeElement('{DAV:}abstract'); + } + if (!empty($privilege['description'])) { + $writer->writeElement('{DAV:}description', $privilege['description']); + } + if (isset($privilege['aggregates'])) { + foreach ($privilege['aggregates'] as $subPrivilege) { + $this->serializePriv($writer, $subPrivilege); + } + } + + $writer->endElement(); // supported-privilege + + } + +} diff --git a/vendor/sabre/dav/lib/DAVACL/Xml/Request/ExpandPropertyReport.php b/vendor/sabre/dav/lib/DAVACL/Xml/Request/ExpandPropertyReport.php new file mode 100644 index 000000000..3f535e301 --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/Xml/Request/ExpandPropertyReport.php @@ -0,0 +1,103 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elems = $reader->parseInnerTree(); + + $obj = new self(); + $obj->properties = self::traverse($elems); + + return $obj; + + } + + /** + * This method is used by deserializeXml, to recursively parse the + * {DAV:}property elements. + * + * @param array $elems + * @return void + */ + private static function traverse($elems) { + + $result = []; + + foreach ($elems as $elem) { + + if ($elem['name'] !== '{DAV:}property') { + continue; + } + + $namespace = isset($elem['attributes']['namespace']) ? + $elem['attributes']['namespace'] : + 'DAV:'; + + $propName = '{' . $namespace . '}' . $elem['attributes']['name']; + + $value = null; + if (is_array($elem['value'])) { + $value = self::traverse($elem['value']); + } + + $result[$propName] = $value; + + } + + return $result; + + } + +} diff --git a/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalPropertySearchReport.php b/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalPropertySearchReport.php new file mode 100644 index 000000000..1e7aa4481 --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalPropertySearchReport.php @@ -0,0 +1,127 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $self = new self(); + + $foundSearchProp = false; + $self->test = 'allof'; + if ($reader->getAttribute('test') === 'anyof') { + $self->test = 'anyof'; + } + + $elemMap = [ + '{DAV:}property-search' => 'Sabre\\Xml\\Element\\KeyValue', + '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue', + ]; + + foreach ($reader->parseInnerTree($elemMap) as $elem) { + + switch ($elem['name']) { + + case '{DAV:}prop' : + $self->properties = array_keys($elem['value']); + break; + case '{DAV:}property-search' : + $foundSearchProp = true; + // This property has two sub-elements: + // {DAV:}prop - The property to be searched on. This may + // also be more than one + // {DAV:}match - The value to match with + if (!isset($elem['value']['{DAV:}prop']) || !isset($elem['value']['{DAV:}match'])) { + throw new BadRequest('The {DAV:}property-search element must contain one {DAV:}match and one {DAV:}prop element'); + } + foreach ($elem['value']['{DAV:}prop'] as $propName => $discard) { + $self->searchProperties[$propName] = $elem['value']['{DAV:}match']; + } + break; + case '{DAV:}apply-to-principal-collection-set' : + $self->applyToPrincipalCollectionSet = true; + break; + + } + + } + if (!$foundSearchProp) { + throw new BadRequest('The {DAV:}principal-property-search report must contain at least 1 {DAV:}property-search element'); + } + + return $self; + + } + +} diff --git a/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalSearchPropertySetReport.php b/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalSearchPropertySetReport.php new file mode 100644 index 000000000..ade157b19 --- /dev/null +++ b/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalSearchPropertySetReport.php @@ -0,0 +1,58 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + if (!$reader->isEmptyElement) { + throw new BadRequest('The {DAV:}principal-search-property-set element must be empty'); + } + + // The element is actually empty, so there's not much to do. + $reader->next(); + + $self = new self(); + return $self; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/AbstractBackend.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/AbstractBackend.php deleted file mode 100644 index c83409eab..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/AbstractBackend.php +++ /dev/null @@ -1,155 +0,0 @@ - array( - * '{DAV:}displayname' => null, - * ), - * 424 => array( - * '{DAV:}owner' => null, - * ) - * ) - * - * In this example it was forbidden to update {DAV:}displayname. - * (403 Forbidden), which in turn also caused {DAV:}owner to fail - * (424 Failed Dependency) because the request needs to be atomic. - * - * @param mixed $calendarId - * @param array $mutations - * @return bool|array - */ - public function updateCalendar($calendarId, array $mutations) { - - return false; - - } - - /** - * Performs a calendar-query on the contents of this calendar. - * - * The calendar-query is defined in RFC4791 : CalDAV. Using the - * calendar-query it is possible for a client to request a specific set of - * object, based on contents of iCalendar properties, date-ranges and - * iCalendar component types (VTODO, VEVENT). - * - * This method should just return a list of (relative) urls that match this - * query. - * - * The list of filters are specified as an array. The exact array is - * documented by \Sabre\CalDAV\CalendarQueryParser. - * - * Note that it is extremely likely that getCalendarObject for every path - * returned from this method will be called almost immediately after. You - * may want to anticipate this to speed up these requests. - * - * This method provides a default implementation, which parses *all* the - * iCalendar objects in the specified calendar. - * - * This default may well be good enough for personal use, and calendars - * that aren't very large. But if you anticipate high usage, big calendars - * or high loads, you are strongly adviced to optimize certain paths. - * - * The best way to do so is override this method and to optimize - * specifically for 'common filters'. - * - * Requests that are extremely common are: - * * requests for just VEVENTS - * * requests for just VTODO - * * requests with a time-range-filter on either VEVENT or VTODO. - * - * ..and combinations of these requests. It may not be worth it to try to - * handle every possible situation and just rely on the (relatively - * easy to use) CalendarQueryValidator to handle the rest. - * - * Note that especially time-range-filters may be difficult to parse. A - * time-range filter specified on a VEVENT must for instance also handle - * recurrence rules correctly. - * A good example of how to interprete all these filters can also simply - * be found in \Sabre\CalDAV\CalendarQueryFilter. This class is as correct - * as possible, so it gives you a good idea on what type of stuff you need - * to think of. - * - * @param mixed $calendarId - * @param array $filters - * @return array - */ - public function calendarQuery($calendarId, array $filters) { - - $result = array(); - $objects = $this->getCalendarObjects($calendarId); - - $validator = new \Sabre\CalDAV\CalendarQueryValidator(); - - foreach($objects as $object) { - - if ($this->validateFilterForObject($object, $filters)) { - $result[] = $object['uri']; - } - - } - - return $result; - - } - - /** - * This method validates if a filters (as passed to calendarQuery) matches - * the given object. - * - * @param array $object - * @param array $filters - * @return bool - */ - protected function validateFilterForObject(array $object, array $filters) { - - // Unfortunately, setting the 'calendardata' here is optional. If - // it was excluded, we actually need another call to get this as - // well. - if (!isset($object['calendardata'])) { - $object = $this->getCalendarObject($object['calendarid'], $object['uri']); - } - - $data = is_resource($object['calendardata'])?stream_get_contents($object['calendardata']):$object['calendardata']; - $vObject = VObject\Reader::read($data); - - $validator = new CalDAV\CalendarQueryValidator(); - return $validator->validate($vObject, $filters); - - } - - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/BackendInterface.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/BackendInterface.php deleted file mode 100644 index 0dc31e5f4..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/BackendInterface.php +++ /dev/null @@ -1,233 +0,0 @@ - array( - * '{DAV:}displayname' => null, - * ), - * 424 => array( - * '{DAV:}owner' => null, - * ) - * ) - * - * In this example it was forbidden to update {DAV:}displayname. - * (403 Forbidden), which in turn also caused {DAV:}owner to fail - * (424 Failed Dependency) because the request needs to be atomic. - * - * @param mixed $calendarId - * @param array $mutations - * @return bool|array - */ - public function updateCalendar($calendarId, array $mutations); - - /** - * Delete a calendar and all it's objects - * - * @param mixed $calendarId - * @return void - */ - public function deleteCalendar($calendarId); - - /** - * Returns all calendar objects within a calendar. - * - * Every item contains an array with the following keys: - * * id - unique identifier which will be used for subsequent updates - * * calendardata - The iCalendar-compatible calendar data - * * uri - a unique key which will be used to construct the uri. This can be any arbitrary string. - * * lastmodified - a timestamp of the last modification time - * * etag - An arbitrary string, surrounded by double-quotes. (e.g.: - * ' "abcdef"') - * * calendarid - The calendarid as it was passed to this function. - * * size - The size of the calendar objects, in bytes. - * - * Note that the etag is optional, but it's highly encouraged to return for - * speed reasons. - * - * The calendardata is also optional. If it's not returned - * 'getCalendarObject' will be called later, which *is* expected to return - * calendardata. - * - * If neither etag or size are specified, the calendardata will be - * used/fetched to determine these numbers. If both are specified the - * amount of times this is needed is reduced by a great degree. - * - * @param mixed $calendarId - * @return array - */ - public function getCalendarObjects($calendarId); - - /** - * Returns information from a single calendar object, based on it's object - * uri. - * - * The returned array must have the same keys as getCalendarObjects. The - * 'calendardata' object is required here though, while it's not required - * for getCalendarObjects. - * - * This method must return null if the object did not exist. - * - * @param mixed $calendarId - * @param string $objectUri - * @return array|null - */ - public function getCalendarObject($calendarId,$objectUri); - - /** - * Creates a new calendar object. - * - * It is possible return an etag from this function, which will be used in - * the response to this PUT request. Note that the ETag must be surrounded - * by double-quotes. - * - * However, you should only really return this ETag if you don't mangle the - * calendar-data. If the result of a subsequent GET to this object is not - * the exact same as this request body, you should omit the ETag. - * - * @param mixed $calendarId - * @param string $objectUri - * @param string $calendarData - * @return string|null - */ - public function createCalendarObject($calendarId,$objectUri,$calendarData); - - /** - * Updates an existing calendarobject, based on it's uri. - * - * It is possible return an etag from this function, which will be used in - * the response to this PUT request. Note that the ETag must be surrounded - * by double-quotes. - * - * However, you should only really return this ETag if you don't mangle the - * calendar-data. If the result of a subsequent GET to this object is not - * the exact same as this request body, you should omit the ETag. - * - * @param mixed $calendarId - * @param string $objectUri - * @param string $calendarData - * @return string|null - */ - public function updateCalendarObject($calendarId,$objectUri,$calendarData); - - /** - * Deletes an existing calendar object. - * - * @param mixed $calendarId - * @param string $objectUri - * @return void - */ - public function deleteCalendarObject($calendarId,$objectUri); - - /** - * Performs a calendar-query on the contents of this calendar. - * - * The calendar-query is defined in RFC4791 : CalDAV. Using the - * calendar-query it is possible for a client to request a specific set of - * object, based on contents of iCalendar properties, date-ranges and - * iCalendar component types (VTODO, VEVENT). - * - * This method should just return a list of (relative) urls that match this - * query. - * - * The list of filters are specified as an array. The exact array is - * documented by Sabre\CalDAV\CalendarQueryParser. - * - * Note that it is extremely likely that getCalendarObject for every path - * returned from this method will be called almost immediately after. You - * may want to anticipate this to speed up these requests. - * - * This method provides a default implementation, which parses *all* the - * iCalendar objects in the specified calendar. - * - * This default may well be good enough for personal use, and calendars - * that aren't very large. But if you anticipate high usage, big calendars - * or high loads, you are strongly adviced to optimize certain paths. - * - * The best way to do so is override this method and to optimize - * specifically for 'common filters'. - * - * Requests that are extremely common are: - * * requests for just VEVENTS - * * requests for just VTODO - * * requests with a time-range-filter on either VEVENT or VTODO. - * - * ..and combinations of these requests. It may not be worth it to try to - * handle every possible situation and just rely on the (relatively - * easy to use) CalendarQueryValidator to handle the rest. - * - * Note that especially time-range-filters may be difficult to parse. A - * time-range filter specified on a VEVENT must for instance also handle - * recurrence rules correctly. - * A good example of how to interprete all these filters can also simply - * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct - * as possible, so it gives you a good idea on what type of stuff you need - * to think of. - * - * @param mixed $calendarId - * @param array $filters - * @return array - */ - public function calendarQuery($calendarId, array $filters); - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/NotificationSupport.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/NotificationSupport.php deleted file mode 100644 index 96533ad6a..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/NotificationSupport.php +++ /dev/null @@ -1,47 +0,0 @@ - 'displayname', - '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description', - '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone', - '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder', - '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor', - ); - - /** - * Creates the backend - * - * @param \PDO $pdo - * @param string $calendarTableName - * @param string $calendarObjectTableName - */ - public function __construct(\PDO $pdo, $calendarTableName = 'calendars', $calendarObjectTableName = 'calendarobjects') { - - $this->pdo = $pdo; - $this->calendarTableName = $calendarTableName; - $this->calendarObjectTableName = $calendarObjectTableName; - - } - - /** - * Returns a list of calendars for a principal. - * - * Every project is an array with the following keys: - * * id, a unique id that will be used by other functions to modify the - * calendar. This can be the same as the uri or a database key. - * * uri, which the basename of the uri with which the calendar is - * accessed. - * * principaluri. The owner of the calendar. Almost always the same as - * principalUri passed to this method. - * - * Furthermore it can contain webdav properties in clark notation. A very - * common one is '{DAV:}displayname'. - * - * @param string $principalUri - * @return array - */ - public function getCalendarsForUser($principalUri) { - - $fields = array_values($this->propertyMap); - $fields[] = 'id'; - $fields[] = 'uri'; - $fields[] = 'ctag'; - $fields[] = 'components'; - $fields[] = 'principaluri'; - $fields[] = 'transparent'; - - // Making fields a comma-delimited list - $fields = implode(', ', $fields); - $stmt = $this->pdo->prepare("SELECT " . $fields . " FROM ".$this->calendarTableName." WHERE principaluri = ? ORDER BY calendarorder ASC"); - $stmt->execute(array($principalUri)); - - $calendars = array(); - while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { - - $components = array(); - if ($row['components']) { - $components = explode(',',$row['components']); - } - - $calendar = array( - 'id' => $row['id'], - 'uri' => $row['uri'], - 'principaluri' => $row['principaluri'], - '{' . CalDAV\Plugin::NS_CALENDARSERVER . '}getctag' => $row['ctag']?$row['ctag']:'0', - '{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Property\SupportedCalendarComponentSet($components), - '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' => new CalDAV\Property\ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'), - ); - - - foreach($this->propertyMap as $xmlName=>$dbName) { - $calendar[$xmlName] = $row[$dbName]; - } - - $calendars[] = $calendar; - - } - - return $calendars; - - } - - /** - * Creates a new calendar for a principal. - * - * If the creation was a success, an id must be returned that can be used to reference - * this calendar in other methods, such as updateCalendar - * - * @param string $principalUri - * @param string $calendarUri - * @param array $properties - * @return string - */ - public function createCalendar($principalUri, $calendarUri, array $properties) { - - $fieldNames = array( - 'principaluri', - 'uri', - 'ctag', - 'transparent', - ); - $values = array( - ':principaluri' => $principalUri, - ':uri' => $calendarUri, - ':ctag' => 1, - ':transparent' => 0, - ); - - // Default value - $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'; - $fieldNames[] = 'components'; - if (!isset($properties[$sccs])) { - $values[':components'] = 'VEVENT,VTODO'; - } else { - if (!($properties[$sccs] instanceof CalDAV\Property\SupportedCalendarComponentSet)) { - throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet'); - } - $values[':components'] = implode(',',$properties[$sccs]->getValue()); - } - $transp = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp'; - if (isset($properties[$transp])) { - $values[':transparent'] = $properties[$transp]->getValue()==='transparent'; - } - - foreach($this->propertyMap as $xmlName=>$dbName) { - if (isset($properties[$xmlName])) { - - $values[':' . $dbName] = $properties[$xmlName]; - $fieldNames[] = $dbName; - } - } - - $stmt = $this->pdo->prepare("INSERT INTO ".$this->calendarTableName." (".implode(', ', $fieldNames).") VALUES (".implode(', ',array_keys($values)).")"); - $stmt->execute($values); - - return $this->pdo->lastInsertId(); - - } - - /** - * Updates properties for a calendar. - * - * The mutations array uses the propertyName in clark-notation as key, - * and the array value for the property value. In the case a property - * should be deleted, the property value will be null. - * - * This method must be atomic. If one property cannot be changed, the - * entire operation must fail. - * - * If the operation was successful, true can be returned. - * If the operation failed, false can be returned. - * - * Deletion of a non-existent property is always successful. - * - * Lastly, it is optional to return detailed information about any - * failures. In this case an array should be returned with the following - * structure: - * - * array( - * 403 => array( - * '{DAV:}displayname' => null, - * ), - * 424 => array( - * '{DAV:}owner' => null, - * ) - * ) - * - * In this example it was forbidden to update {DAV:}displayname. - * (403 Forbidden), which in turn also caused {DAV:}owner to fail - * (424 Failed Dependency) because the request needs to be atomic. - * - * @param string $calendarId - * @param array $mutations - * @return bool|array - */ - public function updateCalendar($calendarId, array $mutations) { - - $newValues = array(); - $result = array( - 200 => array(), // Ok - 403 => array(), // Forbidden - 424 => array(), // Failed Dependency - ); - - $hasError = false; - - foreach($mutations as $propertyName=>$propertyValue) { - - switch($propertyName) { - case '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' : - $fieldName = 'transparent'; - $newValues[$fieldName] = $propertyValue->getValue()==='transparent'; - break; - default : - // Checking the property map - if (!isset($this->propertyMap[$propertyName])) { - // We don't know about this property. - $hasError = true; - $result[403][$propertyName] = null; - unset($mutations[$propertyName]); - continue; - } - - $fieldName = $this->propertyMap[$propertyName]; - $newValues[$fieldName] = $propertyValue; - } - - } - - // If there were any errors we need to fail the request - if ($hasError) { - // Properties has the remaining properties - foreach($mutations as $propertyName=>$propertyValue) { - $result[424][$propertyName] = null; - } - - // Removing unused statuscodes for cleanliness - foreach($result as $status=>$properties) { - if (is_array($properties) && count($properties)===0) unset($result[$status]); - } - - return $result; - - } - - // Success - - // Now we're generating the sql query. - $valuesSql = array(); - foreach($newValues as $fieldName=>$value) { - $valuesSql[] = $fieldName . ' = ?'; - } - $valuesSql[] = 'ctag = ctag + 1'; - - $stmt = $this->pdo->prepare("UPDATE " . $this->calendarTableName . " SET " . implode(', ',$valuesSql) . " WHERE id = ?"); - $newValues['id'] = $calendarId; - $stmt->execute(array_values($newValues)); - - return true; - - } - - /** - * Delete a calendar and all it's objects - * - * @param string $calendarId - * @return void - */ - public function deleteCalendar($calendarId) { - - $stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarObjectTableName.' WHERE calendarid = ?'); - $stmt->execute(array($calendarId)); - - $stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarTableName.' WHERE id = ?'); - $stmt->execute(array($calendarId)); - - } - - /** - * Returns all calendar objects within a calendar. - * - * Every item contains an array with the following keys: - * * id - unique identifier which will be used for subsequent updates - * * calendardata - The iCalendar-compatible calendar data - * * uri - a unique key which will be used to construct the uri. This can be any arbitrary string. - * * lastmodified - a timestamp of the last modification time - * * etag - An arbitrary string, surrounded by double-quotes. (e.g.: - * ' "abcdef"') - * * calendarid - The calendarid as it was passed to this function. - * * size - The size of the calendar objects, in bytes. - * - * Note that the etag is optional, but it's highly encouraged to return for - * speed reasons. - * - * The calendardata is also optional. If it's not returned - * 'getCalendarObject' will be called later, which *is* expected to return - * calendardata. - * - * If neither etag or size are specified, the calendardata will be - * used/fetched to determine these numbers. If both are specified the - * amount of times this is needed is reduced by a great degree. - * - * @param string $calendarId - * @return array - */ - public function getCalendarObjects($calendarId) { - - $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size FROM '.$this->calendarObjectTableName.' WHERE calendarid = ?'); - $stmt->execute(array($calendarId)); - - $result = array(); - foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) { - $result[] = array( - 'id' => $row['id'], - 'uri' => $row['uri'], - 'lastmodified' => $row['lastmodified'], - 'etag' => '"' . $row['etag'] . '"', - 'calendarid' => $row['calendarid'], - 'size' => (int)$row['size'], - ); - } - - return $result; - - } - - /** - * Returns information from a single calendar object, based on it's object - * uri. - * - * The returned array must have the same keys as getCalendarObjects. The - * 'calendardata' object is required here though, while it's not required - * for getCalendarObjects. - * - * This method must return null if the object did not exist. - * - * @param string $calendarId - * @param string $objectUri - * @return array|null - */ - public function getCalendarObject($calendarId,$objectUri) { - - $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, calendardata FROM '.$this->calendarObjectTableName.' WHERE calendarid = ? AND uri = ?'); - $stmt->execute(array($calendarId, $objectUri)); - $row = $stmt->fetch(\PDO::FETCH_ASSOC); - - if(!$row) return null; - - return array( - 'id' => $row['id'], - 'uri' => $row['uri'], - 'lastmodified' => $row['lastmodified'], - 'etag' => '"' . $row['etag'] . '"', - 'calendarid' => $row['calendarid'], - 'size' => (int)$row['size'], - 'calendardata' => $row['calendardata'], - ); - - } - - - /** - * Creates a new calendar object. - * - * It is possible return an etag from this function, which will be used in - * the response to this PUT request. Note that the ETag must be surrounded - * by double-quotes. - * - * However, you should only really return this ETag if you don't mangle the - * calendar-data. If the result of a subsequent GET to this object is not - * the exact same as this request body, you should omit the ETag. - * - * @param mixed $calendarId - * @param string $objectUri - * @param string $calendarData - * @return string|null - */ - public function createCalendarObject($calendarId,$objectUri,$calendarData) { - - $extraData = $this->getDenormalizedData($calendarData); - - $stmt = $this->pdo->prepare('INSERT INTO '.$this->calendarObjectTableName.' (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence) VALUES (?,?,?,?,?,?,?,?,?)'); - $stmt->execute(array( - $calendarId, - $objectUri, - $calendarData, - time(), - $extraData['etag'], - $extraData['size'], - $extraData['componentType'], - $extraData['firstOccurence'], - $extraData['lastOccurence'], - )); - $stmt = $this->pdo->prepare('UPDATE '.$this->calendarTableName.' SET ctag = ctag + 1 WHERE id = ?'); - $stmt->execute(array($calendarId)); - - return '"' . $extraData['etag'] . '"'; - - } - - /** - * Updates an existing calendarobject, based on it's uri. - * - * It is possible return an etag from this function, which will be used in - * the response to this PUT request. Note that the ETag must be surrounded - * by double-quotes. - * - * However, you should only really return this ETag if you don't mangle the - * calendar-data. If the result of a subsequent GET to this object is not - * the exact same as this request body, you should omit the ETag. - * - * @param mixed $calendarId - * @param string $objectUri - * @param string $calendarData - * @return string|null - */ - public function updateCalendarObject($calendarId,$objectUri,$calendarData) { - - $extraData = $this->getDenormalizedData($calendarData); - - $stmt = $this->pdo->prepare('UPDATE '.$this->calendarObjectTableName.' SET calendardata = ?, lastmodified = ?, etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ? WHERE calendarid = ? AND uri = ?'); - $stmt->execute(array($calendarData,time(), $extraData['etag'], $extraData['size'], $extraData['componentType'], $extraData['firstOccurence'], $extraData['lastOccurence'] ,$calendarId,$objectUri)); - $stmt = $this->pdo->prepare('UPDATE '.$this->calendarTableName.' SET ctag = ctag + 1 WHERE id = ?'); - $stmt->execute(array($calendarId)); - - return '"' . $extraData['etag'] . '"'; - - } - - /** - * Parses some information from calendar objects, used for optimized - * calendar-queries. - * - * Returns an array with the following keys: - * * etag - * * size - * * componentType - * * firstOccurence - * * lastOccurence - * - * @param string $calendarData - * @return array - */ - protected function getDenormalizedData($calendarData) { - - $vObject = VObject\Reader::read($calendarData); - $componentType = null; - $component = null; - $firstOccurence = null; - $lastOccurence = null; - foreach($vObject->getComponents() as $component) { - if ($component->name!=='VTIMEZONE') { - $componentType = $component->name; - break; - } - } - if (!$componentType) { - throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component'); - } - if ($componentType === 'VEVENT') { - $firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp(); - // Finding the last occurence is a bit harder - if (!isset($component->RRULE)) { - if (isset($component->DTEND)) { - $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp(); - } elseif (isset($component->DURATION)) { - $endDate = clone $component->DTSTART->getDateTime(); - $endDate->add(VObject\DateTimeParser::parse($component->DURATION->getValue())); - $lastOccurence = $endDate->getTimeStamp(); - } elseif (!$component->DTSTART->hasTime()) { - $endDate = clone $component->DTSTART->getDateTime(); - $endDate->modify('+1 day'); - $lastOccurence = $endDate->getTimeStamp(); - } else { - $lastOccurence = $firstOccurence; - } - } else { - $it = new VObject\RecurrenceIterator($vObject, (string)$component->UID); - $maxDate = new \DateTime(self::MAX_DATE); - if ($it->isInfinite()) { - $lastOccurence = $maxDate->getTimeStamp(); - } else { - $end = $it->getDtEnd(); - while($it->valid() && $end < $maxDate) { - $end = $it->getDtEnd(); - $it->next(); - - } - $lastOccurence = $end->getTimeStamp(); - } - - } - } - - return array( - 'etag' => md5($calendarData), - 'size' => strlen($calendarData), - 'componentType' => $componentType, - 'firstOccurence' => $firstOccurence, - 'lastOccurence' => $lastOccurence, - ); - - } - - /** - * Deletes an existing calendar object. - * - * @param string $calendarId - * @param string $objectUri - * @return void - */ - public function deleteCalendarObject($calendarId,$objectUri) { - - $stmt = $this->pdo->prepare('DELETE FROM '.$this->calendarObjectTableName.' WHERE calendarid = ? AND uri = ?'); - $stmt->execute(array($calendarId,$objectUri)); - $stmt = $this->pdo->prepare('UPDATE '. $this->calendarTableName .' SET ctag = ctag + 1 WHERE id = ?'); - $stmt->execute(array($calendarId)); - - } - - /** - * Performs a calendar-query on the contents of this calendar. - * - * The calendar-query is defined in RFC4791 : CalDAV. Using the - * calendar-query it is possible for a client to request a specific set of - * object, based on contents of iCalendar properties, date-ranges and - * iCalendar component types (VTODO, VEVENT). - * - * This method should just return a list of (relative) urls that match this - * query. - * - * The list of filters are specified as an array. The exact array is - * documented by \Sabre\CalDAV\CalendarQueryParser. - * - * Note that it is extremely likely that getCalendarObject for every path - * returned from this method will be called almost immediately after. You - * may want to anticipate this to speed up these requests. - * - * This method provides a default implementation, which parses *all* the - * iCalendar objects in the specified calendar. - * - * This default may well be good enough for personal use, and calendars - * that aren't very large. But if you anticipate high usage, big calendars - * or high loads, you are strongly adviced to optimize certain paths. - * - * The best way to do so is override this method and to optimize - * specifically for 'common filters'. - * - * Requests that are extremely common are: - * * requests for just VEVENTS - * * requests for just VTODO - * * requests with a time-range-filter on a VEVENT. - * - * ..and combinations of these requests. It may not be worth it to try to - * handle every possible situation and just rely on the (relatively - * easy to use) CalendarQueryValidator to handle the rest. - * - * Note that especially time-range-filters may be difficult to parse. A - * time-range filter specified on a VEVENT must for instance also handle - * recurrence rules correctly. - * A good example of how to interprete all these filters can also simply - * be found in \Sabre\CalDAV\CalendarQueryFilter. This class is as correct - * as possible, so it gives you a good idea on what type of stuff you need - * to think of. - * - * This specific implementation (for the PDO) backend optimizes filters on - * specific components, and VEVENT time-ranges. - * - * @param string $calendarId - * @param array $filters - * @return array - */ - public function calendarQuery($calendarId, array $filters) { - - $result = array(); - $validator = new \Sabre\CalDAV\CalendarQueryValidator(); - - $componentType = null; - $requirePostFilter = true; - $timeRange = null; - - // if no filters were specified, we don't need to filter after a query - if (!$filters['prop-filters'] && !$filters['comp-filters']) { - $requirePostFilter = false; - } - - // Figuring out if there's a component filter - if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) { - $componentType = $filters['comp-filters'][0]['name']; - - // Checking if we need post-filters - if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) { - $requirePostFilter = false; - } - // There was a time-range filter - if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range'])) { - $timeRange = $filters['comp-filters'][0]['time-range']; - - // If start time OR the end time is not specified, we can do a - // 100% accurate mysql query. - if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) { - $requirePostFilter = false; - } - } - - } - - if ($requirePostFilter) { - $query = "SELECT uri, calendardata FROM ".$this->calendarObjectTableName." WHERE calendarid = :calendarid"; - } else { - $query = "SELECT uri FROM ".$this->calendarObjectTableName." WHERE calendarid = :calendarid"; - } - - $values = array( - 'calendarid' => $calendarId, - ); - - if ($componentType) { - $query.=" AND componenttype = :componenttype"; - $values['componenttype'] = $componentType; - } - - if ($timeRange && $timeRange['start']) { - $query.=" AND lastoccurence > :startdate"; - $values['startdate'] = $timeRange['start']->getTimeStamp(); - } - if ($timeRange && $timeRange['end']) { - $query.=" AND firstoccurence < :enddate"; - $values['enddate'] = $timeRange['end']->getTimeStamp(); - } - - $stmt = $this->pdo->prepare($query); - $stmt->execute($values); - - $result = array(); - while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { - if ($requirePostFilter) { - if (!$this->validateFilterForObject($row, $filters)) { - continue; - } - } - $result[] = $row['uri']; - - } - - return $result; - - } -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/SharingSupport.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/SharingSupport.php deleted file mode 100644 index 4538fa5b5..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Backend/SharingSupport.php +++ /dev/null @@ -1,243 +0,0 @@ -caldavBackend = $caldavBackend; - $this->calendarInfo = $calendarInfo; - - } - - /** - * Returns the name of the calendar - * - * @return string - */ - public function getName() { - - return $this->calendarInfo['uri']; - - } - - /** - * Updates properties such as the display name and description - * - * @param array $mutations - * @return array - */ - public function updateProperties($mutations) { - - return $this->caldavBackend->updateCalendar($this->calendarInfo['id'],$mutations); - - } - - /** - * Returns the list of properties - * - * @param array $requestedProperties - * @return array - */ - public function getProperties($requestedProperties) { - - $response = array(); - - foreach($requestedProperties as $prop) switch($prop) { - - case '{urn:ietf:params:xml:ns:caldav}supported-calendar-data' : - $response[$prop] = new Property\SupportedCalendarData(); - break; - case '{urn:ietf:params:xml:ns:caldav}supported-collation-set' : - $response[$prop] = new Property\SupportedCollationSet(); - break; - case '{DAV:}owner' : - $response[$prop] = new DAVACL\Property\Principal(DAVACL\Property\Principal::HREF,$this->calendarInfo['principaluri']); - break; - default : - if (isset($this->calendarInfo[$prop])) $response[$prop] = $this->calendarInfo[$prop]; - break; - - } - return $response; - - } - - /** - * Returns a calendar object - * - * The contained calendar objects are for example Events or Todo's. - * - * @param string $name - * @return \Sabre\CalDAV\ICalendarObject - */ - public function getChild($name) { - - $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'],$name); - - if (!$obj) throw new DAV\Exception\NotFound('Calendar object not found'); - - $obj['acl'] = $this->getACL(); - // Removing the irrelivant - foreach($obj['acl'] as $key=>$acl) { - if ($acl['privilege'] === '{' . Plugin::NS_CALDAV . '}read-free-busy') { - unset($obj['acl'][$key]); - } - } - - return new CalendarObject($this->caldavBackend,$this->calendarInfo,$obj); - - } - - /** - * Returns the full list of calendar objects - * - * @return array - */ - public function getChildren() { - - $objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id']); - $children = array(); - foreach($objs as $obj) { - $obj['acl'] = $this->getACL(); - // Removing the irrelivant - foreach($obj['acl'] as $key=>$acl) { - if ($acl['privilege'] === '{' . Plugin::NS_CALDAV . '}read-free-busy') { - unset($obj['acl'][$key]); - } - } - $children[] = new CalendarObject($this->caldavBackend,$this->calendarInfo,$obj); - } - return $children; - - } - - /** - * Checks if a child-node exists. - * - * @param string $name - * @return bool - */ - public function childExists($name) { - - $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'],$name); - if (!$obj) - return false; - else - return true; - - } - - /** - * Creates a new directory - * - * We actually block this, as subdirectories are not allowed in calendars. - * - * @param string $name - * @return void - */ - public function createDirectory($name) { - - throw new DAV\Exception\MethodNotAllowed('Creating collections in calendar objects is not allowed'); - - } - - /** - * Creates a new file - * - * The contents of the new file must be a valid ICalendar string. - * - * @param string $name - * @param resource $calendarData - * @return string|null - */ - public function createFile($name,$calendarData = null) { - - if (is_resource($calendarData)) { - $calendarData = stream_get_contents($calendarData); - } - return $this->caldavBackend->createCalendarObject($this->calendarInfo['id'],$name,$calendarData); - - } - - /** - * Deletes the calendar. - * - * @return void - */ - public function delete() { - - $this->caldavBackend->deleteCalendar($this->calendarInfo['id']); - - } - - /** - * Renames the calendar. Note that most calendars use the - * {DAV:}displayname to display a name to display a name. - * - * @param string $newName - * @return void - */ - public function setName($newName) { - - throw new DAV\Exception\MethodNotAllowed('Renaming calendars is not yet supported'); - - } - - /** - * Returns the last modification date as a unix timestamp. - * - * @return void - */ - public function getLastModified() { - - return null; - - } - - /** - * Returns the owner principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - public function getOwner() { - - return $this->calendarInfo['principaluri']; - - } - - /** - * Returns a group principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - public function getGroup() { - - return null; - - } - - /** - * Returns a list of ACE's for this node. - * - * Each ACE has the following properties: - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are - * currently the only supported privileges - * * 'principal', a url to the principal who owns the node - * * 'protected' (optional), indicating that this ACE is not allowed to - * be updated. - * - * @return array - */ - public function getACL() { - - return array( - array( - 'privilege' => '{DAV:}read', - 'principal' => $this->getOwner(), - 'protected' => true, - ), - array( - 'privilege' => '{DAV:}write', - 'principal' => $this->getOwner(), - 'protected' => true, - ), - array( - 'privilege' => '{DAV:}read', - 'principal' => $this->getOwner() . '/calendar-proxy-write', - 'protected' => true, - ), - array( - 'privilege' => '{DAV:}write', - 'principal' => $this->getOwner() . '/calendar-proxy-write', - 'protected' => true, - ), - array( - 'privilege' => '{DAV:}read', - 'principal' => $this->getOwner() . '/calendar-proxy-read', - 'protected' => true, - ), - array( - 'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy', - 'principal' => '{DAV:}authenticated', - 'protected' => true, - ), - - ); - - } - - /** - * Updates the ACL - * - * This method will receive a list of new ACE's. - * - * @param array $acl - * @return void - */ - public function setACL(array $acl) { - - throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported'); - - } - - /** - * Returns the list of supported privileges for this node. - * - * The returned data structure is a list of nested privileges. - * See \Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple - * standard structure. - * - * If null is returned from this method, the default privilege set is used, - * which is fine for most common usecases. - * - * @return array|null - */ - public function getSupportedPrivilegeSet() { - - $default = DAVACL\Plugin::getDefaultSupportedPrivilegeSet(); - - // We need to inject 'read-free-busy' in the tree, aggregated under - // {DAV:}read. - foreach($default['aggregates'] as &$agg) { - - if ($agg['privilege'] !== '{DAV:}read') continue; - - $agg['aggregates'][] = array( - 'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy', - ); - - } - return $default; - - } - - /** - * Performs a calendar-query on the contents of this calendar. - * - * The calendar-query is defined in RFC4791 : CalDAV. Using the - * calendar-query it is possible for a client to request a specific set of - * object, based on contents of iCalendar properties, date-ranges and - * iCalendar component types (VTODO, VEVENT). - * - * This method should just return a list of (relative) urls that match this - * query. - * - * The list of filters are specified as an array. The exact array is - * documented by Sabre\CalDAV\CalendarQueryParser. - * - * @param array $filters - * @return array - */ - public function calendarQuery(array $filters) { - - return $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarObject.php b/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarObject.php deleted file mode 100644 index 2b9d2877e..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarObject.php +++ /dev/null @@ -1,279 +0,0 @@ -caldavBackend = $caldavBackend; - - if (!isset($objectData['calendarid'])) { - throw new \InvalidArgumentException('The objectData argument must contain a \'calendarid\' property'); - } - if (!isset($objectData['uri'])) { - throw new \InvalidArgumentException('The objectData argument must contain an \'uri\' property'); - } - - $this->calendarInfo = $calendarInfo; - $this->objectData = $objectData; - - } - - /** - * Returns the uri for this object - * - * @return string - */ - public function getName() { - - return $this->objectData['uri']; - - } - - /** - * Returns the ICalendar-formatted object - * - * @return string - */ - public function get() { - - // Pre-populating the 'calendardata' is optional, if we don't have it - // already we fetch it from the backend. - if (!isset($this->objectData['calendardata'])) { - $this->objectData = $this->caldavBackend->getCalendarObject($this->objectData['calendarid'], $this->objectData['uri']); - } - return $this->objectData['calendardata']; - - } - - /** - * Updates the ICalendar-formatted object - * - * @param string|resource $calendarData - * @return string - */ - public function put($calendarData) { - - if (is_resource($calendarData)) { - $calendarData = stream_get_contents($calendarData); - } - $etag = $this->caldavBackend->updateCalendarObject($this->calendarInfo['id'],$this->objectData['uri'],$calendarData); - $this->objectData['calendardata'] = $calendarData; - $this->objectData['etag'] = $etag; - - return $etag; - - } - - /** - * Deletes the calendar object - * - * @return void - */ - public function delete() { - - $this->caldavBackend->deleteCalendarObject($this->calendarInfo['id'],$this->objectData['uri']); - - } - - /** - * Returns the mime content-type - * - * @return string - */ - public function getContentType() { - - return 'text/calendar; charset=utf-8'; - - } - - /** - * Returns an ETag for this object. - * - * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. - * - * @return string - */ - public function getETag() { - - if (isset($this->objectData['etag'])) { - return $this->objectData['etag']; - } else { - return '"' . md5($this->get()). '"'; - } - - } - - /** - * Returns the last modification date as a unix timestamp - * - * @return int - */ - public function getLastModified() { - - return $this->objectData['lastmodified']; - - } - - /** - * Returns the size of this object in bytes - * - * @return int - */ - public function getSize() { - - if (array_key_exists('size',$this->objectData)) { - return $this->objectData['size']; - } else { - return strlen($this->get()); - } - - } - - /** - * Returns the owner principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - public function getOwner() { - - return $this->calendarInfo['principaluri']; - - } - - /** - * Returns a group principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - public function getGroup() { - - return null; - - } - - /** - * Returns a list of ACE's for this node. - * - * Each ACE has the following properties: - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are - * currently the only supported privileges - * * 'principal', a url to the principal who owns the node - * * 'protected' (optional), indicating that this ACE is not allowed to - * be updated. - * - * @return array - */ - public function getACL() { - - // An alternative acl may be specified in the object data. - if (isset($this->objectData['acl'])) { - return $this->objectData['acl']; - } - - // The default ACL - return array( - array( - 'privilege' => '{DAV:}read', - 'principal' => $this->calendarInfo['principaluri'], - 'protected' => true, - ), - array( - 'privilege' => '{DAV:}write', - 'principal' => $this->calendarInfo['principaluri'], - 'protected' => true, - ), - array( - 'privilege' => '{DAV:}read', - 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write', - 'protected' => true, - ), - array( - 'privilege' => '{DAV:}write', - 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write', - 'protected' => true, - ), - array( - 'privilege' => '{DAV:}read', - 'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read', - 'protected' => true, - ), - - ); - - } - - /** - * Updates the ACL - * - * This method will receive a list of new ACE's. - * - * @param array $acl - * @return void - */ - public function setACL(array $acl) { - - throw new \Sabre\DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported'); - - } - - /** - * Returns the list of supported privileges for this node. - * - * The returned data structure is a list of nested privileges. - * See \Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple - * standard structure. - * - * If null is returned from this method, the default privilege set is used, - * which is fine for most common usecases. - * - * @return array|null - */ - public function getSupportedPrivilegeSet() { - - return null; - - } - -} - diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarQueryParser.php b/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarQueryParser.php deleted file mode 100644 index 0a3472408..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarQueryParser.php +++ /dev/null @@ -1,298 +0,0 @@ -dom = $dom; - $this->xpath = new \DOMXPath($dom); - $this->xpath->registerNameSpace('cal',Plugin::NS_CALDAV); - $this->xpath->registerNameSpace('dav','urn:DAV'); - - } - - /** - * Parses the request. - * - * @return void - */ - public function parse() { - - $filterNode = null; - - $filter = $this->xpath->query('/cal:calendar-query/cal:filter'); - if ($filter->length !== 1) { - throw new \Sabre\DAV\Exception\BadRequest('Only one filter element is allowed'); - } - - $compFilters = $this->parseCompFilters($filter->item(0)); - if (count($compFilters)!==1) { - throw new \Sabre\DAV\Exception\BadRequest('There must be exactly 1 top-level comp-filter.'); - } - - $this->filters = $compFilters[0]; - $this->requestedProperties = array_keys(\Sabre\DAV\XMLUtil::parseProperties($this->dom->firstChild)); - - $expand = $this->xpath->query('/cal:calendar-query/dav:prop/cal:calendar-data/cal:expand'); - if ($expand->length>0) { - $this->expand = $this->parseExpand($expand->item(0)); - } - - - } - - /** - * Parses all the 'comp-filter' elements from a node - * - * @param \DOMElement $parentNode - * @return array - */ - protected function parseCompFilters(\DOMElement $parentNode) { - - $compFilterNodes = $this->xpath->query('cal:comp-filter', $parentNode); - $result = array(); - - for($ii=0; $ii < $compFilterNodes->length; $ii++) { - - $compFilterNode = $compFilterNodes->item($ii); - - $compFilter = array(); - $compFilter['name'] = $compFilterNode->getAttribute('name'); - $compFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $compFilterNode)->length>0; - $compFilter['comp-filters'] = $this->parseCompFilters($compFilterNode); - $compFilter['prop-filters'] = $this->parsePropFilters($compFilterNode); - $compFilter['time-range'] = $this->parseTimeRange($compFilterNode); - - if ($compFilter['time-range'] && !in_array($compFilter['name'],array( - 'VEVENT', - 'VTODO', - 'VJOURNAL', - 'VFREEBUSY', - 'VALARM', - ))) { - throw new \Sabre\DAV\Exception\BadRequest('The time-range filter is not defined for the ' . $compFilter['name'] . ' component'); - }; - - $result[] = $compFilter; - - } - - return $result; - - } - - /** - * Parses all the prop-filter elements from a node - * - * @param \DOMElement $parentNode - * @return array - */ - protected function parsePropFilters(\DOMElement $parentNode) { - - $propFilterNodes = $this->xpath->query('cal:prop-filter', $parentNode); - $result = array(); - - for ($ii=0; $ii < $propFilterNodes->length; $ii++) { - - $propFilterNode = $propFilterNodes->item($ii); - $propFilter = array(); - $propFilter['name'] = $propFilterNode->getAttribute('name'); - $propFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $propFilterNode)->length>0; - $propFilter['param-filters'] = $this->parseParamFilters($propFilterNode); - $propFilter['text-match'] = $this->parseTextMatch($propFilterNode); - $propFilter['time-range'] = $this->parseTimeRange($propFilterNode); - - $result[] = $propFilter; - - } - - return $result; - - } - - /** - * Parses the param-filter element - * - * @param \DOMElement $parentNode - * @return array - */ - protected function parseParamFilters(\DOMElement $parentNode) { - - $paramFilterNodes = $this->xpath->query('cal:param-filter', $parentNode); - $result = array(); - - for($ii=0;$ii<$paramFilterNodes->length;$ii++) { - - $paramFilterNode = $paramFilterNodes->item($ii); - $paramFilter = array(); - $paramFilter['name'] = $paramFilterNode->getAttribute('name'); - $paramFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $paramFilterNode)->length>0; - $paramFilter['text-match'] = $this->parseTextMatch($paramFilterNode); - - $result[] = $paramFilter; - - } - - return $result; - - } - - /** - * Parses the text-match element - * - * @param \DOMElement $parentNode - * @return array|null - */ - protected function parseTextMatch(\DOMElement $parentNode) { - - $textMatchNodes = $this->xpath->query('cal:text-match', $parentNode); - - if ($textMatchNodes->length === 0) - return null; - - $textMatchNode = $textMatchNodes->item(0); - $negateCondition = $textMatchNode->getAttribute('negate-condition'); - $negateCondition = $negateCondition==='yes'; - $collation = $textMatchNode->getAttribute('collation'); - if (!$collation) $collation = 'i;ascii-casemap'; - - return array( - 'negate-condition' => $negateCondition, - 'collation' => $collation, - 'value' => $textMatchNode->nodeValue - ); - - } - - /** - * Parses the time-range element - * - * @param \DOMElement $parentNode - * @return array|null - */ - protected function parseTimeRange(\DOMElement $parentNode) { - - $timeRangeNodes = $this->xpath->query('cal:time-range', $parentNode); - if ($timeRangeNodes->length === 0) { - return null; - } - - $timeRangeNode = $timeRangeNodes->item(0); - - if ($start = $timeRangeNode->getAttribute('start')) { - $start = VObject\DateTimeParser::parseDateTime($start); - } else { - $start = null; - } - if ($end = $timeRangeNode->getAttribute('end')) { - $end = VObject\DateTimeParser::parseDateTime($end); - } else { - $end = null; - } - - if (!is_null($start) && !is_null($end) && $end <= $start) { - throw new \Sabre\DAV\Exception\BadRequest('The end-date must be larger than the start-date in the time-range filter'); - } - - return array( - 'start' => $start, - 'end' => $end, - ); - - } - - /** - * Parses the CALDAV:expand element - * - * @param \DOMElement $parentNode - * @return void - */ - protected function parseExpand(\DOMElement $parentNode) { - - $start = $parentNode->getAttribute('start'); - if(!$start) { - throw new \Sabre\DAV\Exception\BadRequest('The "start" attribute is required for the CALDAV:expand element'); - } - $start = VObject\DateTimeParser::parseDateTime($start); - - $end = $parentNode->getAttribute('end'); - if(!$end) { - throw new \Sabre\DAV\Exception\BadRequest('The "end" attribute is required for the CALDAV:expand element'); - } - - $end = VObject\DateTimeParser::parseDateTime($end); - - if ($end <= $start) { - throw new \Sabre\DAV\Exception\BadRequest('The end-date must be larger than the start-date in the expand element.'); - } - - return array( - 'start' => $start, - 'end' => $end, - ); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarQueryValidator.php b/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarQueryValidator.php deleted file mode 100644 index 494aed1a7..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarQueryValidator.php +++ /dev/null @@ -1,392 +0,0 @@ -name !== $filters['name']) { - return false; - } - - return - $this->validateCompFilters($vObject, $filters['comp-filters']) && - $this->validatePropFilters($vObject, $filters['prop-filters']); - - - } - - /** - * This method checks the validity of comp-filters. - * - * A list of comp-filters needs to be specified. Also the parent of the - * component we're checking should be specified, not the component to check - * itself. - * - * @param VObject\Component $parent - * @param array $filters - * @return bool - */ - protected function validateCompFilters(VObject\Component $parent, array $filters) { - - foreach($filters as $filter) { - - $isDefined = isset($parent->$filter['name']); - - if ($filter['is-not-defined']) { - - if ($isDefined) { - return false; - } else { - continue; - } - - } - if (!$isDefined) { - return false; - } - - if ($filter['time-range']) { - foreach($parent->$filter['name'] as $subComponent) { - if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) { - continue 2; - } - } - return false; - } - - if (!$filter['comp-filters'] && !$filter['prop-filters']) { - continue; - } - - // If there are sub-filters, we need to find at least one component - // for which the subfilters hold true. - foreach($parent->$filter['name'] as $subComponent) { - - if ( - $this->validateCompFilters($subComponent, $filter['comp-filters']) && - $this->validatePropFilters($subComponent, $filter['prop-filters'])) { - // We had a match, so this comp-filter succeeds - continue 2; - } - - } - - // If we got here it means there were sub-comp-filters or - // sub-prop-filters and there was no match. This means this filter - // needs to return false. - return false; - - } - - // If we got here it means we got through all comp-filters alive so the - // filters were all true. - return true; - - } - - /** - * This method checks the validity of prop-filters. - * - * A list of prop-filters needs to be specified. Also the parent of the - * property we're checking should be specified, not the property to check - * itself. - * - * @param VObject\Component $parent - * @param array $filters - * @return bool - */ - protected function validatePropFilters(VObject\Component $parent, array $filters) { - - foreach($filters as $filter) { - - $isDefined = isset($parent->$filter['name']); - - if ($filter['is-not-defined']) { - - if ($isDefined) { - return false; - } else { - continue; - } - - } - if (!$isDefined) { - return false; - } - - if ($filter['time-range']) { - foreach($parent->$filter['name'] as $subComponent) { - if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) { - continue 2; - } - } - return false; - } - - if (!$filter['param-filters'] && !$filter['text-match']) { - continue; - } - - // If there are sub-filters, we need to find at least one property - // for which the subfilters hold true. - foreach($parent->$filter['name'] as $subComponent) { - - if( - $this->validateParamFilters($subComponent, $filter['param-filters']) && - (!$filter['text-match'] || $this->validateTextMatch($subComponent, $filter['text-match'])) - ) { - // We had a match, so this prop-filter succeeds - continue 2; - } - - } - - // If we got here it means there were sub-param-filters or - // text-match filters and there was no match. This means the - // filter needs to return false. - return false; - - } - - // If we got here it means we got through all prop-filters alive so the - // filters were all true. - return true; - - } - - /** - * This method checks the validity of param-filters. - * - * A list of param-filters needs to be specified. Also the parent of the - * parameter we're checking should be specified, not the parameter to check - * itself. - * - * @param VObject\Property $parent - * @param array $filters - * @return bool - */ - protected function validateParamFilters(VObject\Property $parent, array $filters) { - - foreach($filters as $filter) { - - $isDefined = isset($parent[$filter['name']]); - - if ($filter['is-not-defined']) { - - if ($isDefined) { - return false; - } else { - continue; - } - - } - if (!$isDefined) { - return false; - } - - if (!$filter['text-match']) { - continue; - } - - if (version_compare(VObject\Version::VERSION, '3.0.0beta1', '>=')) { - - // If there are sub-filters, we need to find at least one parameter - // for which the subfilters hold true. - foreach($parent[$filter['name']]->getParts() as $subParam) { - - if($this->validateTextMatch($subParam,$filter['text-match'])) { - // We had a match, so this param-filter succeeds - continue 2; - } - - } - - } else { - - // If there are sub-filters, we need to find at least one parameter - // for which the subfilters hold true. - foreach($parent[$filter['name']] as $subParam) { - - if($this->validateTextMatch($subParam,$filter['text-match'])) { - // We had a match, so this param-filter succeeds - continue 2; - } - - } - - } - - // If we got here it means there was a text-match filter and there - // were no matches. This means the filter needs to return false. - return false; - - } - - // If we got here it means we got through all param-filters alive so the - // filters were all true. - return true; - - } - - /** - * This method checks the validity of a text-match. - * - * A single text-match should be specified as well as the specific property - * or parameter we need to validate. - * - * @param VObject\Node|string $check Value to check against. - * @param array $textMatch - * @return bool - */ - protected function validateTextMatch($check, array $textMatch) { - - if ($check instanceof VObject\Node) { - $check = (string)$check; - } - - $isMatching = \Sabre\DAV\StringUtil::textMatch($check, $textMatch['value'], $textMatch['collation']); - - return ($textMatch['negate-condition'] xor $isMatching); - - } - - /** - * Validates if a component matches the given time range. - * - * This is all based on the rules specified in rfc4791, which are quite - * complex. - * - * @param VObject\Node $component - * @param DateTime $start - * @param DateTime $end - * @return bool - */ - protected function validateTimeRange(VObject\Node $component, $start, $end) { - - if (is_null($start)) { - $start = new DateTime('1900-01-01'); - } - if (is_null($end)) { - $end = new DateTime('3000-01-01'); - } - - switch($component->name) { - - case 'VEVENT' : - case 'VTODO' : - case 'VJOURNAL' : - - return $component->isInTimeRange($start, $end); - - case 'VALARM' : - - // If the valarm is wrapped in a recurring event, we need to - // expand the recursions, and validate each. - // - // Our datamodel doesn't easily allow us to do this straight - // in the VALARM component code, so this is a hack, and an - // expensive one too. - if ($component->parent->name === 'VEVENT' && $component->parent->RRULE) { - - // Fire up the iterator! - $it = new VObject\RecurrenceIterator($component->parent->parent, (string)$component->parent->UID); - while($it->valid()) { - $expandedEvent = $it->getEventObject(); - - // We need to check from these expanded alarms, which - // one is the first to trigger. Based on this, we can - // determine if we can 'give up' expanding events. - $firstAlarm = null; - if ($expandedEvent->VALARM !== null) { - foreach($expandedEvent->VALARM as $expandedAlarm) { - - $effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime(); - if ($expandedAlarm->isInTimeRange($start, $end)) { - return true; - } - - if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') { - // This is an alarm with a non-relative trigger - // time, likely created by a buggy client. The - // implication is that every alarm in this - // recurring event trigger at the exact same - // time. It doesn't make sense to traverse - // further. - } else { - // We store the first alarm as a means to - // figure out when we can stop traversing. - if (!$firstAlarm || $effectiveTrigger < $firstAlarm) { - $firstAlarm = $effectiveTrigger; - } - } - } - } - if (is_null($firstAlarm)) { - // No alarm was found. - // - // Or technically: No alarm that will change for - // every instance of the recurrence was found, - // which means we can assume there was no match. - return false; - } - if ($firstAlarm > $end) { - return false; - } - $it->next(); - } - return false; - } else { - return $component->isInTimeRange($start, $end); - } - - case 'VFREEBUSY' : - throw new \Sabre\DAV\Exception\NotImplemented('time-range filters are currently not supported on ' . $component->name . ' components'); - - case 'COMPLETED' : - case 'CREATED' : - case 'DTEND' : - case 'DTSTAMP' : - case 'DTSTART' : - case 'DUE' : - case 'LAST-MODIFIED' : - return ($start <= $component->getDateTime() && $end >= $component->getDateTime()); - - - - default : - throw new \Sabre\DAV\Exception\BadRequest('You cannot create a time-range filter on a ' . $component->name . ' component'); - - } - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarRootNode.php b/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarRootNode.php deleted file mode 100644 index 4f72ad444..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/CalendarRootNode.php +++ /dev/null @@ -1,77 +0,0 @@ -caldavBackend = $caldavBackend; - - } - - /** - * Returns the nodename - * - * We're overriding this, because the default will be the 'principalPrefix', - * and we want it to be Sabre\CalDAV\Plugin::CALENDAR_ROOT - * - * @return string - */ - public function getName() { - - return Plugin::CALENDAR_ROOT; - - } - - /** - * This method returns a node for a principal. - * - * The passed array contains principal information, and is guaranteed to - * at least contain a uri item. Other properties may or may not be - * supplied by the authentication backend. - * - * @param array $principal - * @return \Sabre\DAV\INode - */ - public function getChildForPrincipal(array $principal) { - - return new UserCalendars($this->caldavBackend, $principal); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Exception/InvalidComponentType.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Exception/InvalidComponentType.php deleted file mode 100644 index f2a64e33f..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Exception/InvalidComponentType.php +++ /dev/null @@ -1,35 +0,0 @@ -ownerDocument; - - $np = $doc->createElementNS(CalDAV\Plugin::NS_CALDAV,'cal:supported-calendar-component'); - $errorNode->appendChild($np); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/ICSExportPlugin.php b/vendor/sabre/dav/lib/Sabre/CalDAV/ICSExportPlugin.php deleted file mode 100644 index bba7fbd5a..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/ICSExportPlugin.php +++ /dev/null @@ -1,142 +0,0 @@ -server = $server; - $this->server->subscribeEvent('beforeMethod',array($this,'beforeMethod'), 90); - - } - - /** - * 'beforeMethod' event handles. This event handles intercepts GET requests ending - * with ?export - * - * @param string $method - * @param string $uri - * @return bool - */ - public function beforeMethod($method, $uri) { - - if ($method!='GET') return; - if ($this->server->httpRequest->getQueryString()!='export') return; - - // splitting uri - list($uri) = explode('?',$uri,2); - - $node = $this->server->tree->getNodeForPath($uri); - - if (!($node instanceof Calendar)) return; - - // Checking ACL, if available. - if ($aclPlugin = $this->server->getPlugin('acl')) { - $aclPlugin->checkPrivileges($uri, '{DAV:}read'); - } - - $this->server->httpResponse->setHeader('Content-Type','text/calendar'); - $this->server->httpResponse->sendStatus(200); - - $nodes = $this->server->getPropertiesForPath($uri, array( - '{' . Plugin::NS_CALDAV . '}calendar-data', - ),1); - - $this->server->httpResponse->sendBody($this->generateICS($nodes)); - - // Returning false to break the event chain - return false; - - } - - /** - * Merges all calendar objects, and builds one big ics export - * - * @param array $nodes - * @return string - */ - public function generateICS(array $nodes) { - - $calendar = new VObject\Component\VCalendar(); - $calendar->version = '2.0'; - if (DAV\Server::$exposeVersion) { - $calendar->prodid = '-//SabreDAV//SabreDAV ' . DAV\Version::VERSION . '//EN'; - } else { - $calendar->prodid = '-//SabreDAV//SabreDAV//EN'; - } - $calendar->calscale = 'GREGORIAN'; - - $collectedTimezones = array(); - - $timezones = array(); - $objects = array(); - - foreach($nodes as $node) { - - if (!isset($node[200]['{' . Plugin::NS_CALDAV . '}calendar-data'])) { - continue; - } - $nodeData = $node[200]['{' . Plugin::NS_CALDAV . '}calendar-data']; - - $nodeComp = VObject\Reader::read($nodeData); - - foreach($nodeComp->children() as $child) { - - switch($child->name) { - case 'VEVENT' : - case 'VTODO' : - case 'VJOURNAL' : - $objects[] = $child; - break; - - // VTIMEZONE is special, because we need to filter out the duplicates - case 'VTIMEZONE' : - // Naively just checking tzid. - if (in_array((string)$child->TZID, $collectedTimezones)) continue; - - $timezones[] = $child; - $collectedTimezones[] = $child->TZID; - break; - - } - - } - - } - - foreach($timezones as $tz) $calendar->add($tz); - foreach($objects as $obj) $calendar->add($obj); - - return $calendar->serialize(); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/ICalendar.php b/vendor/sabre/dav/lib/Sabre/CalDAV/ICalendar.php deleted file mode 100644 index 0f0547046..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/ICalendar.php +++ /dev/null @@ -1,36 +0,0 @@ -caldavBackend = $caldavBackend; - $this->principalUri = $principalUri; - - } - - /** - * Returns all notifications for a principal - * - * @return array - */ - public function getChildren() { - - $children = array(); - $notifications = $this->caldavBackend->getNotificationsForPrincipal($this->principalUri); - - foreach($notifications as $notification) { - - $children[] = new Node( - $this->caldavBackend, - $this->principalUri, - $notification - ); - } - - return $children; - - } - - /** - * Returns the name of this object - * - * @return string - */ - public function getName() { - - return 'notifications'; - - } - - /** - * Returns the owner principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - public function getOwner() { - - return $this->principalUri; - - } - - /** - * Returns a group principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - public function getGroup() { - - return null; - - } - - /** - * Returns a list of ACE's for this node. - * - * Each ACE has the following properties: - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are - * currently the only supported privileges - * * 'principal', a url to the principal who owns the node - * * 'protected' (optional), indicating that this ACE is not allowed to - * be updated. - * - * @return array - */ - public function getACL() { - - return array( - array( - 'principal' => $this->getOwner(), - 'privilege' => '{DAV:}read', - 'protected' => true, - ), - array( - 'principal' => $this->getOwner(), - 'privilege' => '{DAV:}write', - 'protected' => true, - ) - ); - - } - - /** - * Updates the ACL - * - * This method will receive a list of new ACE's as an array argument. - * - * @param array $acl - * @return void - */ - public function setACL(array $acl) { - - throw new DAV\Exception\NotImplemented('Updating ACLs is not implemented here'); - - } - - /** - * Returns the list of supported privileges for this node. - * - * The returned data structure is a list of nested privileges. - * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple - * standard structure. - * - * If null is returned from this method, the default privilege set is used, - * which is fine for most common usecases. - * - * @return array|null - */ - public function getSupportedPrivilegeSet() { - - return null; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/ICollection.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/ICollection.php deleted file mode 100644 index 26e13b211..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/ICollection.php +++ /dev/null @@ -1,24 +0,0 @@ -caldavBackend = $caldavBackend; - $this->principalUri = $principalUri; - $this->notification = $notification; - - } - - /** - * Returns the path name for this notification - * - * @return id - */ - public function getName() { - - return $this->notification->getId() . '.xml'; - - } - - /** - * Returns the etag for the notification. - * - * The etag must be surrounded by litteral double-quotes. - * - * @return string - */ - public function getETag() { - - return $this->notification->getETag(); - - } - - /** - * This method must return an xml element, using the - * Sabre\CalDAV\Notifications\INotificationType classes. - * - * @return INotificationType - */ - public function getNotificationType() { - - return $this->notification; - - } - - /** - * Deletes this notification - * - * @return void - */ - public function delete() { - - $this->caldavBackend->deleteNotification($this->getOwner(), $this->notification); - - } - - /** - * Returns the owner principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - public function getOwner() { - - return $this->principalUri; - - } - - /** - * Returns a group principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - public function getGroup() { - - return null; - - } - - /** - * Returns a list of ACE's for this node. - * - * Each ACE has the following properties: - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are - * currently the only supported privileges - * * 'principal', a url to the principal who owns the node - * * 'protected' (optional), indicating that this ACE is not allowed to - * be updated. - * - * @return array - */ - public function getACL() { - - return array( - array( - 'principal' => $this->getOwner(), - 'privilege' => '{DAV:}read', - 'protected' => true, - ), - array( - 'principal' => $this->getOwner(), - 'privilege' => '{DAV:}write', - 'protected' => true, - ) - ); - - } - - /** - * Updates the ACL - * - * This method will receive a list of new ACE's as an array argument. - * - * @param array $acl - * @return void - */ - public function setACL(array $acl) { - - throw new DAV\Exception\NotImplemented('Updating ACLs is not implemented here'); - - } - - /** - * Returns the list of supported privileges for this node. - * - * The returned data structure is a list of nested privileges. - * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple - * standard structure. - * - * If null is returned from this method, the default privilege set is used, - * which is fine for most common usecases. - * - * @return array|null - */ - public function getSupportedPrivilegeSet() { - - return null; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/Invite.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/Invite.php deleted file mode 100644 index 8d6974d45..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/Invite.php +++ /dev/null @@ -1,324 +0,0 @@ -$value) { - if (!property_exists($this, $key)) { - throw new \InvalidArgumentException('Unknown option: ' . $key); - } - $this->$key = $value; - } - - } - - /** - * Serializes the notification as a single property. - * - * You should usually just encode the single top-level element of the - * notification. - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - public function serialize(DAV\Server $server, \DOMElement $node) { - - $prop = $node->ownerDocument->createElement('cs:invite-notification'); - $node->appendChild($prop); - - } - - /** - * This method serializes the entire notification, as it is used in the - * response body. - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - public function serializeBody(DAV\Server $server, \DOMElement $node) { - - $doc = $node->ownerDocument; - - $dt = $doc->createElement('cs:dtstamp'); - $this->dtStamp->setTimezone(new \DateTimezone('GMT')); - $dt->appendChild($doc->createTextNode($this->dtStamp->format('Ymd\\THis\\Z'))); - $node->appendChild($dt); - - $prop = $doc->createElement('cs:invite-notification'); - $node->appendChild($prop); - - $uid = $doc->createElement('cs:uid'); - $uid->appendChild( $doc->createTextNode($this->id) ); - $prop->appendChild($uid); - - $href = $doc->createElement('d:href'); - $href->appendChild( $doc->createTextNode( $this->href ) ); - $prop->appendChild($href); - - $nodeName = null; - switch($this->type) { - - case SharingPlugin::STATUS_ACCEPTED : - $nodeName = 'cs:invite-accepted'; - break; - case SharingPlugin::STATUS_DECLINED : - $nodeName = 'cs:invite-declined'; - break; - case SharingPlugin::STATUS_DELETED : - $nodeName = 'cs:invite-deleted'; - break; - case SharingPlugin::STATUS_NORESPONSE : - $nodeName = 'cs:invite-noresponse'; - break; - - } - $prop->appendChild( - $doc->createElement($nodeName) - ); - $hostHref = $doc->createElement('d:href', $server->getBaseUri() . $this->hostUrl); - $hostUrl = $doc->createElement('cs:hosturl'); - $hostUrl->appendChild($hostHref); - $prop->appendChild($hostUrl); - - $access = $doc->createElement('cs:access'); - if ($this->readOnly) { - $access->appendChild($doc->createElement('cs:read')); - } else { - $access->appendChild($doc->createElement('cs:read-write')); - } - $prop->appendChild($access); - - $organizerUrl = $doc->createElement('cs:organizer'); - // If the organizer contains a 'mailto:' part, it means it should be - // treated as absolute. - if (strtolower(substr($this->organizer,0,7))==='mailto:') { - $organizerHref = new DAV\Property\Href($this->organizer, false); - } else { - $organizerHref = new DAV\Property\Href($this->organizer, true); - } - $organizerHref->serialize($server, $organizerUrl); - - if ($this->commonName) { - $commonName = $doc->createElement('cs:common-name'); - $commonName->appendChild($doc->createTextNode($this->commonName)); - $organizerUrl->appendChild($commonName); - - $commonNameOld = $doc->createElement('cs:organizer-cn'); - $commonNameOld->appendChild($doc->createTextNode($this->commonName)); - $prop->appendChild($commonNameOld); - - } - if ($this->firstName) { - $firstName = $doc->createElement('cs:first-name'); - $firstName->appendChild($doc->createTextNode($this->firstName)); - $organizerUrl->appendChild($firstName); - - $firstNameOld = $doc->createElement('cs:organizer-first'); - $firstNameOld->appendChild($doc->createTextNode($this->firstName)); - $prop->appendChild($firstNameOld); - } - if ($this->lastName) { - $lastName = $doc->createElement('cs:last-name'); - $lastName->appendChild($doc->createTextNode($this->lastName)); - $organizerUrl->appendChild($lastName); - - $lastNameOld = $doc->createElement('cs:organizer-last'); - $lastNameOld->appendChild($doc->createTextNode($this->lastName)); - $prop->appendChild($lastNameOld); - } - $prop->appendChild($organizerUrl); - - if ($this->summary) { - $summary = $doc->createElement('cs:summary'); - $summary->appendChild($doc->createTextNode($this->summary)); - $prop->appendChild($summary); - } - if ($this->supportedComponents) { - - $xcomp = $doc->createElement('cal:supported-calendar-component-set'); - $this->supportedComponents->serialize($server, $xcomp); - $prop->appendChild($xcomp); - - } - - } - - /** - * Returns a unique id for this notification - * - * This is just the base url. This should generally be some kind of unique - * id. - * - * @return string - */ - public function getId() { - - return $this->id; - - } - - /** - * Returns the ETag for this notification. - * - * The ETag must be surrounded by literal double-quotes. - * - * @return string - */ - public function getETag() { - - return $this->etag; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/InviteReply.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/InviteReply.php deleted file mode 100644 index e40751346..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/InviteReply.php +++ /dev/null @@ -1,218 +0,0 @@ -$value) { - if (!property_exists($this, $key)) { - throw new \InvalidArgumentException('Unknown option: ' . $key); - } - $this->$key = $value; - } - - } - - /** - * Serializes the notification as a single property. - * - * You should usually just encode the single top-level element of the - * notification. - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - public function serialize(DAV\Server $server, \DOMElement $node) { - - $prop = $node->ownerDocument->createElement('cs:invite-reply'); - $node->appendChild($prop); - - } - - /** - * This method serializes the entire notification, as it is used in the - * response body. - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - public function serializeBody(DAV\Server $server, \DOMElement $node) { - - $doc = $node->ownerDocument; - - $dt = $doc->createElement('cs:dtstamp'); - $this->dtStamp->setTimezone(new \DateTimezone('GMT')); - $dt->appendChild($doc->createTextNode($this->dtStamp->format('Ymd\\THis\\Z'))); - $node->appendChild($dt); - - $prop = $doc->createElement('cs:invite-reply'); - $node->appendChild($prop); - - $uid = $doc->createElement('cs:uid'); - $uid->appendChild($doc->createTextNode($this->id)); - $prop->appendChild($uid); - - $inReplyTo = $doc->createElement('cs:in-reply-to'); - $inReplyTo->appendChild( $doc->createTextNode($this->inReplyTo) ); - $prop->appendChild($inReplyTo); - - $href = $doc->createElement('d:href'); - $href->appendChild( $doc->createTextNode($this->href) ); - $prop->appendChild($href); - - $nodeName = null; - switch($this->type) { - - case SharingPlugin::STATUS_ACCEPTED : - $nodeName = 'cs:invite-accepted'; - break; - case SharingPlugin::STATUS_DECLINED : - $nodeName = 'cs:invite-declined'; - break; - - } - $prop->appendChild( - $doc->createElement($nodeName) - ); - $hostHref = $doc->createElement('d:href', $server->getBaseUri() . $this->hostUrl); - $hostUrl = $doc->createElement('cs:hosturl'); - $hostUrl->appendChild($hostHref); - $prop->appendChild($hostUrl); - - if ($this->summary) { - $summary = $doc->createElement('cs:summary'); - $summary->appendChild($doc->createTextNode($this->summary)); - $prop->appendChild($summary); - } - - } - - /** - * Returns a unique id for this notification - * - * This is just the base url. This should generally be some kind of unique - * id. - * - * @return string - */ - public function getId() { - - return $this->id; - - } - - /** - * Returns the ETag for this notification. - * - * The ETag must be surrounded by literal double-quotes. - * - * @return string - */ - public function getETag() { - - return $this->etag; - - } -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/SystemStatus.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/SystemStatus.php deleted file mode 100644 index 608892dab..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Notifications/Notification/SystemStatus.php +++ /dev/null @@ -1,182 +0,0 @@ -id = $id; - $this->type = $type; - $this->description = $description; - $this->href = $href; - $this->etag = $etag; - - } - - /** - * Serializes the notification as a single property. - * - * You should usually just encode the single top-level element of the - * notification. - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - public function serialize(DAV\Server $server, \DOMElement $node) { - - switch($this->type) { - case self::TYPE_LOW : - $type = 'low'; - break; - case self::TYPE_MEDIUM : - $type = 'medium'; - break; - default : - case self::TYPE_HIGH : - $type = 'high'; - break; - } - - $prop = $node->ownerDocument->createElement('cs:systemstatus'); - $prop->setAttribute('type', $type); - - $node->appendChild($prop); - - } - - /** - * This method serializes the entire notification, as it is used in the - * response body. - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - public function serializeBody(DAV\Server $server, \DOMElement $node) { - - switch($this->type) { - case self::TYPE_LOW : - $type = 'low'; - break; - case self::TYPE_MEDIUM : - $type = 'medium'; - break; - default : - case self::TYPE_HIGH : - $type = 'high'; - break; - } - - $prop = $node->ownerDocument->createElement('cs:systemstatus'); - $prop->setAttribute('type', $type); - - if ($this->description) { - $text = $node->ownerDocument->createTextNode($this->description); - $desc = $node->ownerDocument->createElement('cs:description'); - $desc->appendChild($text); - $prop->appendChild($desc); - } - if ($this->href) { - $text = $node->ownerDocument->createTextNode($this->href); - $href = $node->ownerDocument->createElement('d:href'); - $href->appendChild($text); - $prop->appendChild($href); - } - - $node->appendChild($prop); - - } - - /** - * Returns a unique id for this notification - * - * This is just the base url. This should generally be some kind of unique - * id. - * - * @return string - */ - public function getId() { - - return $this->id; - - } - - /* - * Returns the ETag for this notification. - * - * The ETag must be surrounded by literal double-quotes. - * - * @return string - */ - public function getETag() { - - return $this->etag; - - } -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Plugin.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Plugin.php deleted file mode 100644 index 610929388..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Plugin.php +++ /dev/null @@ -1,1338 +0,0 @@ -imipHandler = $imipHandler; - - } - - /** - * Use this method to tell the server this plugin defines additional - * HTTP methods. - * - * This method is passed a uri. It should only return HTTP methods that are - * available for the specified uri. - * - * @param string $uri - * @return array - */ - public function getHTTPMethods($uri) { - - // The MKCALENDAR is only available on unmapped uri's, whose - // parents extend IExtendedCollection - list($parent, $name) = DAV\URLUtil::splitPath($uri); - - $node = $this->server->tree->getNodeForPath($parent); - - if ($node instanceof DAV\IExtendedCollection) { - try { - $node->getChild($name); - } catch (DAV\Exception\NotFound $e) { - return array('MKCALENDAR'); - } - } - return array(); - - } - - /** - * Returns a list of features for the DAV: HTTP header. - * - * @return array - */ - public function getFeatures() { - - return array('calendar-access', 'calendar-proxy'); - - } - - /** - * Returns a plugin name. - * - * Using this name other plugins will be able to access other plugins - * using DAV\Server::getPlugin - * - * @return string - */ - public function getPluginName() { - - return 'caldav'; - - } - - /** - * Returns a list of reports this plugin supports. - * - * This will be used in the {DAV:}supported-report-set property. - * Note that you still need to subscribe to the 'report' event to actually - * implement them - * - * @param string $uri - * @return array - */ - public function getSupportedReportSet($uri) { - - $node = $this->server->tree->getNodeForPath($uri); - - $reports = array(); - if ($node instanceof ICalendar || $node instanceof ICalendarObject) { - $reports[] = '{' . self::NS_CALDAV . '}calendar-multiget'; - $reports[] = '{' . self::NS_CALDAV . '}calendar-query'; - } - if ($node instanceof ICalendar) { - $reports[] = '{' . self::NS_CALDAV . '}free-busy-query'; - } - return $reports; - - } - - /** - * Initializes the plugin - * - * @param DAV\Server $server - * @return void - */ - public function initialize(DAV\Server $server) { - - $this->server = $server; - - $server->subscribeEvent('unknownMethod',array($this,'unknownMethod')); - //$server->subscribeEvent('unknownMethod',array($this,'unknownMethod2'),1000); - $server->subscribeEvent('report',array($this,'report')); - $server->subscribeEvent('beforeGetProperties',array($this,'beforeGetProperties')); - $server->subscribeEvent('onHTMLActionsPanel', array($this,'htmlActionsPanel')); - $server->subscribeEvent('onBrowserPostAction', array($this,'browserPostAction')); - $server->subscribeEvent('beforeWriteContent', array($this, 'beforeWriteContent')); - $server->subscribeEvent('beforeCreateFile', array($this, 'beforeCreateFile')); - $server->subscribeEvent('beforeMethod', array($this,'beforeMethod')); - - $server->xmlNamespaces[self::NS_CALDAV] = 'cal'; - $server->xmlNamespaces[self::NS_CALENDARSERVER] = 'cs'; - - $server->propertyMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Property\\SupportedCalendarComponentSet'; - $server->propertyMap['{' . self::NS_CALDAV . '}schedule-calendar-transp'] = 'Sabre\\CalDAV\\Property\\ScheduleCalendarTransp'; - - $server->resourceTypeMapping['\\Sabre\\CalDAV\\ICalendar'] = '{urn:ietf:params:xml:ns:caldav}calendar'; - $server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IOutbox'] = '{urn:ietf:params:xml:ns:caldav}schedule-outbox'; - $server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyRead'] = '{http://calendarserver.org/ns/}calendar-proxy-read'; - $server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyWrite'] = '{http://calendarserver.org/ns/}calendar-proxy-write'; - $server->resourceTypeMapping['\\Sabre\\CalDAV\\Notifications\\ICollection'] = '{' . self::NS_CALENDARSERVER . '}notification'; - - array_push($server->protectedProperties, - - '{' . self::NS_CALDAV . '}supported-calendar-component-set', - '{' . self::NS_CALDAV . '}supported-calendar-data', - '{' . self::NS_CALDAV . '}max-resource-size', - '{' . self::NS_CALDAV . '}min-date-time', - '{' . self::NS_CALDAV . '}max-date-time', - '{' . self::NS_CALDAV . '}max-instances', - '{' . self::NS_CALDAV . '}max-attendees-per-instance', - '{' . self::NS_CALDAV . '}calendar-home-set', - '{' . self::NS_CALDAV . '}supported-collation-set', - '{' . self::NS_CALDAV . '}calendar-data', - - // scheduling extension - '{' . self::NS_CALDAV . '}schedule-inbox-URL', - '{' . self::NS_CALDAV . '}schedule-outbox-URL', - '{' . self::NS_CALDAV . '}calendar-user-address-set', - '{' . self::NS_CALDAV . '}calendar-user-type', - - // CalendarServer extensions - '{' . self::NS_CALENDARSERVER . '}getctag', - '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for', - '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for', - '{' . self::NS_CALENDARSERVER . '}notification-URL', - '{' . self::NS_CALENDARSERVER . '}notificationtype' - - ); - } - - /** - * This function handles support for the MKCALENDAR method - * - * @param string $method - * @param string $uri - * @return bool - */ - public function unknownMethod($method, $uri) { - - switch ($method) { - case 'MKCALENDAR' : - $this->httpMkCalendar($uri); - // false is returned to stop the propagation of the - // unknownMethod event. - return false; - case 'POST' : - - // Checking if this is a text/calendar content type - $contentType = $this->server->httpRequest->getHeader('Content-Type'); - if (strpos($contentType, 'text/calendar')!==0) { - return; - } - - // Checking if we're talking to an outbox - try { - $node = $this->server->tree->getNodeForPath($uri); - } catch (DAV\Exception\NotFound $e) { - return; - } - if (!$node instanceof Schedule\IOutbox) - return; - - $this->outboxRequest($node, $uri); - return false; - - } - - } - - /** - * This functions handles REPORT requests specific to CalDAV - * - * @param string $reportName - * @param \DOMNode $dom - * @return bool - */ - public function report($reportName,$dom) { - - switch($reportName) { - case '{'.self::NS_CALDAV.'}calendar-multiget' : - $this->calendarMultiGetReport($dom); - return false; - case '{'.self::NS_CALDAV.'}calendar-query' : - $this->calendarQueryReport($dom); - return false; - case '{'.self::NS_CALDAV.'}free-busy-query' : - $this->freeBusyQueryReport($dom); - return false; - - } - - - } - - /** - * This function handles the MKCALENDAR HTTP method, which creates - * a new calendar. - * - * @param string $uri - * @return void - */ - public function httpMkCalendar($uri) { - - // Due to unforgivable bugs in iCal, we're completely disabling MKCALENDAR support - // for clients matching iCal in the user agent - //$ua = $this->server->httpRequest->getHeader('User-Agent'); - //if (strpos($ua,'iCal/')!==false) { - // throw new \Sabre\DAV\Exception\Forbidden('iCal has major bugs in it\'s RFC3744 support. Therefore we are left with no other choice but disabling this feature.'); - //} - - $body = $this->server->httpRequest->getBody(true); - $properties = array(); - - if ($body) { - - $dom = DAV\XMLUtil::loadDOMDocument($body); - - foreach($dom->firstChild->childNodes as $child) { - - if (DAV\XMLUtil::toClarkNotation($child)!=='{DAV:}set') continue; - foreach(DAV\XMLUtil::parseProperties($child,$this->server->propertyMap) as $k=>$prop) { - $properties[$k] = $prop; - } - - } - } - - $resourceType = array('{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar'); - - $this->server->createCollection($uri,$resourceType,$properties); - - $this->server->httpResponse->sendStatus(201); - $this->server->httpResponse->setHeader('Content-Length',0); - } - - /** - * beforeGetProperties - * - * This method handler is invoked before any after properties for a - * resource are fetched. This allows us to add in any CalDAV specific - * properties. - * - * @param string $path - * @param DAV\INode $node - * @param array $requestedProperties - * @param array $returnedProperties - * @return void - */ - public function beforeGetProperties($path, DAV\INode $node, &$requestedProperties, &$returnedProperties) { - - if ($node instanceof DAVACL\IPrincipal) { - - // calendar-home-set property - $calHome = '{' . self::NS_CALDAV . '}calendar-home-set'; - if (in_array($calHome,$requestedProperties)) { - $principalId = $node->getName(); - $calendarHomePath = self::CALENDAR_ROOT . '/' . $principalId . '/'; - - unset($requestedProperties[array_search($calHome, $requestedProperties)]); - $returnedProperties[200][$calHome] = new DAV\Property\Href($calendarHomePath); - - } - - // schedule-outbox-URL property - $scheduleProp = '{' . self::NS_CALDAV . '}schedule-outbox-URL'; - if (in_array($scheduleProp,$requestedProperties)) { - $principalId = $node->getName(); - $outboxPath = self::CALENDAR_ROOT . '/' . $principalId . '/outbox'; - - unset($requestedProperties[array_search($scheduleProp, $requestedProperties)]); - $returnedProperties[200][$scheduleProp] = new DAV\Property\Href($outboxPath); - - } - - // calendar-user-address-set property - $calProp = '{' . self::NS_CALDAV . '}calendar-user-address-set'; - if (in_array($calProp,$requestedProperties)) { - - $addresses = $node->getAlternateUriSet(); - $addresses[] = $this->server->getBaseUri() . DAV\URLUtil::encodePath($node->getPrincipalUrl() . '/'); - unset($requestedProperties[array_search($calProp, $requestedProperties)]); - $returnedProperties[200][$calProp] = new DAV\Property\HrefList($addresses, false); - - } - - // These two properties are shortcuts for ical to easily find - // other principals this principal has access to. - $propRead = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for'; - $propWrite = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for'; - if (in_array($propRead,$requestedProperties) || in_array($propWrite,$requestedProperties)) { - - $aclPlugin = $this->server->getPlugin('acl'); - $membership = $aclPlugin->getPrincipalMembership($path); - $readList = array(); - $writeList = array(); - - foreach($membership as $group) { - - $groupNode = $this->server->tree->getNodeForPath($group); - - // If the node is either ap proxy-read or proxy-write - // group, we grab the parent principal and add it to the - // list. - if ($groupNode instanceof Principal\IProxyRead) { - list($readList[]) = DAV\URLUtil::splitPath($group); - } - if ($groupNode instanceof Principal\IProxyWrite) { - list($writeList[]) = DAV\URLUtil::splitPath($group); - } - - } - if (in_array($propRead,$requestedProperties)) { - unset($requestedProperties[$propRead]); - $returnedProperties[200][$propRead] = new DAV\Property\HrefList($readList); - } - if (in_array($propWrite,$requestedProperties)) { - unset($requestedProperties[$propWrite]); - $returnedProperties[200][$propWrite] = new DAV\Property\HrefList($writeList); - } - - } - - // notification-URL property - $notificationUrl = '{' . self::NS_CALENDARSERVER . '}notification-URL'; - if (($index = array_search($notificationUrl, $requestedProperties)) !== false) { - $principalId = $node->getName(); - $calendarHomePath = 'calendars/' . $principalId . '/notifications/'; - unset($requestedProperties[$index]); - $returnedProperties[200][$notificationUrl] = new DAV\Property\Href($calendarHomePath); - } - - } // instanceof IPrincipal - - if ($node instanceof Notifications\INode) { - - $propertyName = '{' . self::NS_CALENDARSERVER . '}notificationtype'; - if (($index = array_search($propertyName, $requestedProperties)) !== false) { - - $returnedProperties[200][$propertyName] = - $node->getNotificationType(); - - unset($requestedProperties[$index]); - - } - - } // instanceof Notifications_INode - - - if ($node instanceof ICalendarObject) { - // The calendar-data property is not supposed to be a 'real' - // property, but in large chunks of the spec it does act as such. - // Therefore we simply expose it as a property. - $calDataProp = '{' . Plugin::NS_CALDAV . '}calendar-data'; - if (in_array($calDataProp, $requestedProperties)) { - unset($requestedProperties[$calDataProp]); - $val = $node->get(); - if (is_resource($val)) - $val = stream_get_contents($val); - - // Taking out \r to not screw up the xml output - $returnedProperties[200][$calDataProp] = str_replace("\r","", $val); - - } - } - - } - - /** - * This function handles the calendar-multiget REPORT. - * - * This report is used by the client to fetch the content of a series - * of urls. Effectively avoiding a lot of redundant requests. - * - * @param \DOMNode $dom - * @return void - */ - public function calendarMultiGetReport($dom) { - - $properties = array_keys(DAV\XMLUtil::parseProperties($dom->firstChild)); - $hrefElems = $dom->getElementsByTagNameNS('urn:DAV','href'); - - $xpath = new \DOMXPath($dom); - $xpath->registerNameSpace('cal',Plugin::NS_CALDAV); - $xpath->registerNameSpace('dav','urn:DAV'); - - $expand = $xpath->query('/cal:calendar-multiget/dav:prop/cal:calendar-data/cal:expand'); - if ($expand->length>0) { - $expandElem = $expand->item(0); - $start = $expandElem->getAttribute('start'); - $end = $expandElem->getAttribute('end'); - if(!$start || !$end) { - throw new DAV\Exception\BadRequest('The "start" and "end" attributes are required for the CALDAV:expand element'); - } - $start = VObject\DateTimeParser::parseDateTime($start); - $end = VObject\DateTimeParser::parseDateTime($end); - - if ($end <= $start) { - throw new DAV\Exception\BadRequest('The end-date must be larger than the start-date in the expand element.'); - } - - $expand = true; - - } else { - - $expand = false; - - } - - foreach($hrefElems as $elem) { - $uri = $this->server->calculateUri($elem->nodeValue); - list($objProps) = $this->server->getPropertiesForPath($uri,$properties); - - if ($expand && isset($objProps[200]['{' . self::NS_CALDAV . '}calendar-data'])) { - $vObject = VObject\Reader::read($objProps[200]['{' . self::NS_CALDAV . '}calendar-data']); - $vObject->expand($start, $end); - $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); - } - - $propertyList[]=$objProps; - - } - - $prefer = $this->server->getHTTPPRefer(); - - $this->server->httpResponse->sendStatus(207); - $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); - $this->server->httpResponse->setHeader('Vary','Brief,Prefer'); - $this->server->httpResponse->sendBody($this->server->generateMultiStatus($propertyList, $prefer['return-minimal'])); - - } - - /** - * This function handles the calendar-query REPORT - * - * This report is used by clients to request calendar objects based on - * complex conditions. - * - * @param \DOMNode $dom - * @return void - */ - public function calendarQueryReport($dom) { - - $parser = new CalendarQueryParser($dom); - $parser->parse(); - - $node = $this->server->tree->getNodeForPath($this->server->getRequestUri()); - $depth = $this->server->getHTTPDepth(0); - - // The default result is an empty array - $result = array(); - - // The calendarobject was requested directly. In this case we handle - // this locally. - if ($depth == 0 && $node instanceof ICalendarObject) { - - $requestedCalendarData = true; - $requestedProperties = $parser->requestedProperties; - - if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) { - - // We always retrieve calendar-data, as we need it for filtering. - $requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data'; - - // If calendar-data wasn't explicitly requested, we need to remove - // it after processing. - $requestedCalendarData = false; - } - - $properties = $this->server->getPropertiesForPath( - $this->server->getRequestUri(), - $requestedProperties, - 0 - ); - - // This array should have only 1 element, the first calendar - // object. - $properties = current($properties); - - // If there wasn't any calendar-data returned somehow, we ignore - // this. - if (isset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) { - - $validator = new CalendarQueryValidator(); - - $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); - if ($validator->validate($vObject,$parser->filters)) { - - // If the client didn't require the calendar-data property, - // we won't give it back. - if (!$requestedCalendarData) { - unset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); - } else { - if ($parser->expand) { - $vObject->expand($parser->expand['start'], $parser->expand['end']); - $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); - } - } - - $result = array($properties); - - } - - } - - } - // If we're dealing with a calendar, the calendar itself is responsible - // for the calendar-query. - if ($node instanceof ICalendar && $depth = 1) { - - $nodePaths = $node->calendarQuery($parser->filters); - - foreach($nodePaths as $path) { - - list($properties) = - $this->server->getPropertiesForPath($this->server->getRequestUri() . '/' . $path, $parser->requestedProperties); - - if ($parser->expand) { - // We need to do some post-processing - $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); - $vObject->expand($parser->expand['start'], $parser->expand['end']); - $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); - } - - $result[] = $properties; - - } - - } - - $prefer = $this->server->getHTTPPRefer(); - - $this->server->httpResponse->sendStatus(207); - $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); - $this->server->httpResponse->setHeader('Vary','Brief,Prefer'); - $this->server->httpResponse->sendBody($this->server->generateMultiStatus($result, $prefer['return-minimal'])); - - } - - /** - * This method is responsible for parsing the request and generating the - * response for the CALDAV:free-busy-query REPORT. - * - * @param \DOMNode $dom - * @return void - */ - protected function freeBusyQueryReport(\DOMNode $dom) { - - $start = null; - $end = null; - - foreach($dom->firstChild->childNodes as $childNode) { - - $clark = DAV\XMLUtil::toClarkNotation($childNode); - if ($clark == '{' . self::NS_CALDAV . '}time-range') { - $start = $childNode->getAttribute('start'); - $end = $childNode->getAttribute('end'); - break; - } - - } - if ($start) { - $start = VObject\DateTimeParser::parseDateTime($start); - } - if ($end) { - $end = VObject\DateTimeParser::parseDateTime($end); - } - - if (!$start && !$end) { - throw new DAV\Exception\BadRequest('The freebusy report must have a time-range filter'); - } - $acl = $this->server->getPlugin('acl'); - - if (!$acl) { - throw new DAV\Exception('The ACL plugin must be loaded for free-busy queries to work'); - } - $uri = $this->server->getRequestUri(); - $acl->checkPrivileges($uri,'{' . self::NS_CALDAV . '}read-free-busy'); - - $calendar = $this->server->tree->getNodeForPath($uri); - if (!$calendar instanceof ICalendar) { - throw new DAV\Exception\NotImplemented('The free-busy-query REPORT is only implemented on calendars'); - } - - // Doing a calendar-query first, to make sure we get the most - // performance. - $urls = $calendar->calendarQuery(array( - 'name' => 'VCALENDAR', - 'comp-filters' => array( - array( - 'name' => 'VEVENT', - 'comp-filters' => array(), - 'prop-filters' => array(), - 'is-not-defined' => false, - 'time-range' => array( - 'start' => $start, - 'end' => $end, - ), - ), - ), - 'prop-filters' => array(), - 'is-not-defined' => false, - 'time-range' => null, - )); - - $objects = array_map(function($url) use ($calendar) { - $obj = $calendar->getChild($url)->get(); - return $obj; - }, $urls); - - $generator = new VObject\FreeBusyGenerator(); - $generator->setObjects($objects); - $generator->setTimeRange($start, $end); - $result = $generator->getResult(); - $result = $result->serialize(); - - $this->server->httpResponse->sendStatus(200); - $this->server->httpResponse->setHeader('Content-Type', 'text/calendar'); - $this->server->httpResponse->setHeader('Content-Length', strlen($result)); - $this->server->httpResponse->sendBody($result); - - } - - /** - * This method is triggered before a file gets updated with new content. - * - * This plugin uses this method to ensure that CalDAV objects receive - * valid calendar data. - * - * @param string $path - * @param DAV\IFile $node - * @param resource $data - * @return void - */ - public function beforeWriteContent($path, DAV\IFile $node, &$data) { - - if (!$node instanceof ICalendarObject) - return; - - $this->validateICalendar($data, $path); - - } - - /** - * This method is triggered before a new file is created. - * - * This plugin uses this method to ensure that newly created calendar - * objects contain valid calendar data. - * - * @param string $path - * @param resource $data - * @param DAV\ICollection $parentNode - * @return void - */ - public function beforeCreateFile($path, &$data, DAV\ICollection $parentNode) { - - if (!$parentNode instanceof Calendar) - return; - - $this->validateICalendar($data, $path); - - } - - /** - * This event is triggered before any HTTP request is handled. - * - * We use this to intercept GET calls to notification nodes, and return the - * proper response. - * - * @param string $method - * @param string $path - * @return void - */ - public function beforeMethod($method, $path) { - - if ($method!=='GET') return; - - try { - $node = $this->server->tree->getNodeForPath($path); - } catch (DAV\Exception\NotFound $e) { - return; - } - - if (!$node instanceof Notifications\INode) - return; - - if (!$this->server->checkPreconditions(true)) return false; - $dom = new \DOMDocument('1.0', 'UTF-8'); - - $dom->formatOutput = true; - - $root = $dom->createElement('cs:notification'); - foreach($this->server->xmlNamespaces as $namespace => $prefix) { - $root->setAttribute('xmlns:' . $prefix, $namespace); - } - - $dom->appendChild($root); - $node->getNotificationType()->serializeBody($this->server, $root); - - $this->server->httpResponse->setHeader('Content-Type','application/xml'); - $this->server->httpResponse->setHeader('ETag',$node->getETag()); - $this->server->httpResponse->sendStatus(200); - $this->server->httpResponse->sendBody($dom->saveXML()); - - return false; - - } - - /** - * Checks if the submitted iCalendar data is in fact, valid. - * - * An exception is thrown if it's not. - * - * @param resource|string $data - * @param string $path - * @return void - */ - protected function validateICalendar(&$data, $path) { - - // If it's a stream, we convert it to a string first. - if (is_resource($data)) { - $data = stream_get_contents($data); - } - - // Converting the data to unicode, if needed. - $data = DAV\StringUtil::ensureUTF8($data); - - try { - - $vobj = VObject\Reader::read($data); - - } catch (VObject\ParseException $e) { - - throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid iCalendar 2.0 data. Parse error: ' . $e->getMessage()); - - } - - if ($vobj->name !== 'VCALENDAR') { - throw new DAV\Exception\UnsupportedMediaType('This collection can only support iCalendar objects.'); - } - - // Get the Supported Components for the target calendar - list($parentPath,$object) = DAV\URLUtil::splitPath($path); - $calendarProperties = $this->server->getProperties($parentPath,array('{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set')); - $supportedComponents = $calendarProperties['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set']->getValue(); - - $foundType = null; - $foundUID = null; - foreach($vobj->getComponents() as $component) { - switch($component->name) { - case 'VTIMEZONE' : - continue 2; - case 'VEVENT' : - case 'VTODO' : - case 'VJOURNAL' : - if (is_null($foundType)) { - $foundType = $component->name; - if (!in_array($foundType, $supportedComponents)) { - throw new Exception\InvalidComponentType('This calendar only supports ' . implode(', ', $supportedComponents) . '. We found a ' . $foundType); - } - if (!isset($component->UID)) { - throw new DAV\Exception\BadRequest('Every ' . $component->name . ' component must have an UID'); - } - $foundUID = (string)$component->UID; - } else { - if ($foundType !== $component->name) { - throw new DAV\Exception\BadRequest('A calendar object must only contain 1 component. We found a ' . $component->name . ' as well as a ' . $foundType); - } - if ($foundUID !== (string)$component->UID) { - throw new DAV\Exception\BadRequest('Every ' . $component->name . ' in this object must have identical UIDs'); - } - } - break; - default : - throw new DAV\Exception\BadRequest('You are not allowed to create components of type: ' . $component->name . ' here'); - - } - } - if (!$foundType) - throw new DAV\Exception\BadRequest('iCalendar object must contain at least 1 of VEVENT, VTODO or VJOURNAL'); - - } - - /** - * This method handles POST requests to the schedule-outbox. - * - * Currently, two types of requests are support: - * * FREEBUSY requests from RFC 6638 - * * Simple iTIP messages from draft-desruisseaux-caldav-sched-04 - * - * The latter is from an expired early draft of the CalDAV scheduling - * extensions, but iCal depends on a feature from that spec, so we - * implement it. - * - * @param Schedule\IOutbox $outboxNode - * @param string $outboxUri - * @return void - */ - public function outboxRequest(Schedule\IOutbox $outboxNode, $outboxUri) { - - // Parsing the request body - try { - $vObject = VObject\Reader::read($this->server->httpRequest->getBody(true)); - } catch (VObject\ParseException $e) { - throw new DAV\Exception\BadRequest('The request body must be a valid iCalendar object. Parse error: ' . $e->getMessage()); - } - - // The incoming iCalendar object must have a METHOD property, and a - // component. The combination of both determines what type of request - // this is. - $componentType = null; - foreach($vObject->getComponents() as $component) { - if ($component->name !== 'VTIMEZONE') { - $componentType = $component->name; - break; - } - } - if (is_null($componentType)) { - throw new DAV\Exception\BadRequest('We expected at least one VTODO, VJOURNAL, VFREEBUSY or VEVENT component'); - } - - // Validating the METHOD - $method = strtoupper((string)$vObject->METHOD); - if (!$method) { - throw new DAV\Exception\BadRequest('A METHOD property must be specified in iTIP messages'); - } - - // So we support two types of requests: - // - // REQUEST with a VFREEBUSY component - // REQUEST, REPLY, ADD, CANCEL on VEVENT components - - $acl = $this->server->getPlugin('acl'); - - if ($componentType === 'VFREEBUSY' && $method === 'REQUEST') { - - $acl && $acl->checkPrivileges($outboxUri,'{' . Plugin::NS_CALDAV . '}schedule-query-freebusy'); - $this->handleFreeBusyRequest($outboxNode, $vObject); - - } elseif ($componentType === 'VEVENT' && in_array($method, array('REQUEST','REPLY','ADD','CANCEL'))) { - - $acl && $acl->checkPrivileges($outboxUri,'{' . Plugin::NS_CALDAV . '}schedule-post-vevent'); - $this->handleEventNotification($outboxNode, $vObject); - - } else { - - throw new DAV\Exception\NotImplemented('SabreDAV supports only VFREEBUSY (REQUEST) and VEVENT (REQUEST, REPLY, ADD, CANCEL)'); - - } - - } - - /** - * This method handles the REQUEST, REPLY, ADD and CANCEL methods for - * VEVENT iTip messages. - * - * @return void - */ - protected function handleEventNotification(Schedule\IOutbox $outboxNode, VObject\Component $vObject) { - - $originator = $this->server->httpRequest->getHeader('Originator'); - $recipients = $this->server->httpRequest->getHeader('Recipient'); - - if (!$originator) { - throw new DAV\Exception\BadRequest('The Originator: header must be specified when making POST requests'); - } - if (!$recipients) { - throw new DAV\Exception\BadRequest('The Recipient: header must be specified when making POST requests'); - } - - $recipients = explode(',',$recipients); - foreach($recipients as $k=>$recipient) { - - $recipient = trim($recipient); - if (!preg_match('/^mailto:(.*)@(.*)$/i', $recipient)) { - throw new DAV\Exception\BadRequest('Recipients must start with mailto: and must be valid email address'); - } - $recipient = substr($recipient, 7); - $recipients[$k] = $recipient; - } - - // We need to make sure that 'originator' matches one of the email - // addresses of the selected principal. - $principal = $outboxNode->getOwner(); - $props = $this->server->getProperties($principal,array( - '{' . self::NS_CALDAV . '}calendar-user-address-set', - )); - - $addresses = array(); - if (isset($props['{' . self::NS_CALDAV . '}calendar-user-address-set'])) { - $addresses = $props['{' . self::NS_CALDAV . '}calendar-user-address-set']->getHrefs(); - } - - $found = false; - foreach($addresses as $address) { - - // Trimming the / on both sides, just in case.. - if (rtrim(strtolower($originator),'/') === rtrim(strtolower($address),'/')) { - $found = true; - break; - } - - } - - if (!$found) { - throw new DAV\Exception\Forbidden('The addresses specified in the Originator header did not match any addresses in the owners calendar-user-address-set header'); - } - - // If the Originator header was a url, and not a mailto: address.. - // we're going to try to pull the mailto: from the vobject body. - if (strtolower(substr($originator,0,7)) !== 'mailto:') { - $originator = (string)$vObject->VEVENT->ORGANIZER; - - } - if (strtolower(substr($originator,0,7)) !== 'mailto:') { - throw new DAV\Exception\Forbidden('Could not find mailto: address in both the Orignator header, and the ORGANIZER property in the VEVENT'); - } - $originator = substr($originator,7); - - $result = $this->iMIPMessage($originator, $recipients, $vObject, $principal); - $this->server->httpResponse->sendStatus(200); - $this->server->httpResponse->setHeader('Content-Type','application/xml'); - $this->server->httpResponse->sendBody($this->generateScheduleResponse($result)); - - } - - /** - * Sends an iMIP message by email. - * - * This method must return an array with status codes per recipient. - * This should look something like: - * - * array( - * 'user1@example.org' => '2.0;Success' - * ) - * - * Formatting for this status code can be found at: - * https://tools.ietf.org/html/rfc5545#section-3.8.8.3 - * - * A list of valid status codes can be found at: - * https://tools.ietf.org/html/rfc5546#section-3.6 - * - * @param string $originator - * @param array $recipients - * @param VObject\Component $vObject - * @param string $principal Principal url - * @return array - */ - protected function iMIPMessage($originator, array $recipients, VObject\Component $vObject, $principal) { - - if (!$this->imipHandler) { - $resultStatus = '5.2;This server does not support this operation'; - } else { - $this->imipHandler->sendMessage($originator, $recipients, $vObject, $principal); - $resultStatus = '2.0;Success'; - } - - $result = array(); - foreach($recipients as $recipient) { - $result[$recipient] = $resultStatus; - } - - return $result; - - } - - /** - * Generates a schedule-response XML body - * - * The recipients array is a key->value list, containing email addresses - * and iTip status codes. See the iMIPMessage method for a description of - * the value. - * - * @param array $recipients - * @return string - */ - public function generateScheduleResponse(array $recipients) { - - $dom = new \DOMDocument('1.0','utf-8'); - $dom->formatOutput = true; - $xscheduleResponse = $dom->createElement('cal:schedule-response'); - $dom->appendChild($xscheduleResponse); - - foreach($this->server->xmlNamespaces as $namespace=>$prefix) { - - $xscheduleResponse->setAttribute('xmlns:' . $prefix, $namespace); - - } - - foreach($recipients as $recipient=>$status) { - $xresponse = $dom->createElement('cal:response'); - - $xrecipient = $dom->createElement('cal:recipient'); - $xrecipient->appendChild($dom->createTextNode($recipient)); - $xresponse->appendChild($xrecipient); - - $xrequestStatus = $dom->createElement('cal:request-status'); - $xrequestStatus->appendChild($dom->createTextNode($status)); - $xresponse->appendChild($xrequestStatus); - - $xscheduleResponse->appendChild($xresponse); - - } - - return $dom->saveXML(); - - } - - /** - * This method is responsible for parsing a free-busy query request and - * returning it's result. - * - * @param Schedule\IOutbox $outbox - * @param string $request - * @return string - */ - protected function handleFreeBusyRequest(Schedule\IOutbox $outbox, VObject\Component $vObject) { - - $vFreeBusy = $vObject->VFREEBUSY; - $organizer = $vFreeBusy->organizer; - - $organizer = (string)$organizer; - - // Validating if the organizer matches the owner of the inbox. - $owner = $outbox->getOwner(); - - $caldavNS = '{' . Plugin::NS_CALDAV . '}'; - - $uas = $caldavNS . 'calendar-user-address-set'; - $props = $this->server->getProperties($owner,array($uas)); - - if (empty($props[$uas]) || !in_array($organizer, $props[$uas]->getHrefs())) { - throw new DAV\Exception\Forbidden('The organizer in the request did not match any of the addresses for the owner of this inbox'); - } - - if (!isset($vFreeBusy->ATTENDEE)) { - throw new DAV\Exception\BadRequest('You must at least specify 1 attendee'); - } - - $attendees = array(); - foreach($vFreeBusy->ATTENDEE as $attendee) { - $attendees[]= (string)$attendee; - } - - - if (!isset($vFreeBusy->DTSTART) || !isset($vFreeBusy->DTEND)) { - throw new DAV\Exception\BadRequest('DTSTART and DTEND must both be specified'); - } - - $startRange = $vFreeBusy->DTSTART->getDateTime(); - $endRange = $vFreeBusy->DTEND->getDateTime(); - - $results = array(); - foreach($attendees as $attendee) { - $results[] = $this->getFreeBusyForEmail($attendee, $startRange, $endRange, $vObject); - } - - $dom = new \DOMDocument('1.0','utf-8'); - $dom->formatOutput = true; - $scheduleResponse = $dom->createElement('cal:schedule-response'); - foreach($this->server->xmlNamespaces as $namespace=>$prefix) { - - $scheduleResponse->setAttribute('xmlns:' . $prefix,$namespace); - - } - $dom->appendChild($scheduleResponse); - - foreach($results as $result) { - $response = $dom->createElement('cal:response'); - - $recipient = $dom->createElement('cal:recipient'); - $recipientHref = $dom->createElement('d:href'); - - $recipientHref->appendChild($dom->createTextNode($result['href'])); - $recipient->appendChild($recipientHref); - $response->appendChild($recipient); - - $reqStatus = $dom->createElement('cal:request-status'); - $reqStatus->appendChild($dom->createTextNode($result['request-status'])); - $response->appendChild($reqStatus); - - if (isset($result['calendar-data'])) { - - $calendardata = $dom->createElement('cal:calendar-data'); - $calendardata->appendChild($dom->createTextNode(str_replace("\r\n","\n",$result['calendar-data']->serialize()))); - $response->appendChild($calendardata); - - } - $scheduleResponse->appendChild($response); - } - - $this->server->httpResponse->sendStatus(200); - $this->server->httpResponse->setHeader('Content-Type','application/xml'); - $this->server->httpResponse->sendBody($dom->saveXML()); - - } - - /** - * Returns free-busy information for a specific address. The returned - * data is an array containing the following properties: - * - * calendar-data : A VFREEBUSY VObject - * request-status : an iTip status code. - * href: The principal's email address, as requested - * - * The following request status codes may be returned: - * * 2.0;description - * * 3.7;description - * - * @param string $email address - * @param \DateTime $start - * @param \DateTime $end - * @param VObject\Component $request - * @return array - */ - protected function getFreeBusyForEmail($email, \DateTime $start, \DateTime $end, VObject\Component $request) { - - $caldavNS = '{' . Plugin::NS_CALDAV . '}'; - - $aclPlugin = $this->server->getPlugin('acl'); - if (substr($email,0,7)==='mailto:') $email = substr($email,7); - - $result = $aclPlugin->principalSearch( - array('{http://sabredav.org/ns}email-address' => $email), - array( - '{DAV:}principal-URL', $caldavNS . 'calendar-home-set', - '{http://sabredav.org/ns}email-address', - ) - ); - - if (!count($result)) { - return array( - 'request-status' => '3.7;Could not find principal', - 'href' => 'mailto:' . $email, - ); - } - - if (!isset($result[0][200][$caldavNS . 'calendar-home-set'])) { - return array( - 'request-status' => '3.7;No calendar-home-set property found', - 'href' => 'mailto:' . $email, - ); - } - $homeSet = $result[0][200][$caldavNS . 'calendar-home-set']->getHref(); - - // Grabbing the calendar list - $objects = array(); - foreach($this->server->tree->getNodeForPath($homeSet)->getChildren() as $node) { - if (!$node instanceof ICalendar) { - continue; - } - $aclPlugin->checkPrivileges($homeSet . $node->getName() ,$caldavNS . 'read-free-busy'); - - // Getting the list of object uris within the time-range - $urls = $node->calendarQuery(array( - 'name' => 'VCALENDAR', - 'comp-filters' => array( - array( - 'name' => 'VEVENT', - 'comp-filters' => array(), - 'prop-filters' => array(), - 'is-not-defined' => false, - 'time-range' => array( - 'start' => $start, - 'end' => $end, - ), - ), - ), - 'prop-filters' => array(), - 'is-not-defined' => false, - 'time-range' => null, - )); - - $calObjects = array_map(function($url) use ($node) { - $obj = $node->getChild($url)->get(); - return $obj; - }, $urls); - - $objects = array_merge($objects,$calObjects); - - } - - $vcalendar = new VObject\Component\VCalendar(); - $vcalendar->VERSION = '2.0'; - $vcalendar->METHOD = 'REPLY'; - $vcalendar->CALSCALE = 'GREGORIAN'; - $vcalendar->PRODID = '-//SabreDAV//SabreDAV ' . DAV\Version::VERSION . '//EN'; - - $generator = new VObject\FreeBusyGenerator(); - $generator->setObjects($objects); - $generator->setTimeRange($start, $end); - $generator->setBaseObject($vcalendar); - - $result = $generator->getResult(); - - $vcalendar->VFREEBUSY->ATTENDEE = 'mailto:' . $email; - $vcalendar->VFREEBUSY->UID = (string)$request->VFREEBUSY->UID; - $vcalendar->VFREEBUSY->ORGANIZER = clone $request->VFREEBUSY->ORGANIZER; - - return array( - 'calendar-data' => $result, - 'request-status' => '2.0;Success', - 'href' => 'mailto:' . $email, - ); - } - - /** - * This method is used to generate HTML output for the - * DAV\Browser\Plugin. This allows us to generate an interface users - * can use to create new calendars. - * - * @param DAV\INode $node - * @param string $output - * @return bool - */ - public function htmlActionsPanel(DAV\INode $node, &$output) { - - if (!$node instanceof UserCalendars) - return; - - $output.= '
    -

    Create new calendar

    - -
    -
    - -
    - '; - - return false; - - } - - /** - * This method allows us to intercept the 'mkcalendar' sabreAction. This - * action enables the user to create new calendars from the browser plugin. - * - * @param string $uri - * @param string $action - * @param array $postVars - * @return bool - */ - public function browserPostAction($uri, $action, array $postVars) { - - if ($action!=='mkcalendar') - return; - - $resourceType = array('{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar'); - $properties = array(); - if (isset($postVars['{DAV:}displayname'])) { - $properties['{DAV:}displayname'] = $postVars['{DAV:}displayname']; - } - $this->server->createCollection($uri . '/' . $postVars['name'],$resourceType,$properties); - return false; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/Collection.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/Collection.php deleted file mode 100644 index 8a747a644..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/Collection.php +++ /dev/null @@ -1,32 +0,0 @@ -principalBackend, $principalInfo); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/IProxyRead.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/IProxyRead.php deleted file mode 100644 index 548411fa8..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/IProxyRead.php +++ /dev/null @@ -1,19 +0,0 @@ -principalInfo = $principalInfo; - $this->principalBackend = $principalBackend; - - } - - /** - * Returns this principals name. - * - * @return string - */ - public function getName() { - - return 'calendar-proxy-read'; - - } - - /** - * Returns the last modification time - * - * @return null - */ - public function getLastModified() { - - return null; - - } - - /** - * Deletes the current node - * - * @throws DAV\Exception\Forbidden - * @return void - */ - public function delete() { - - throw new DAV\Exception\Forbidden('Permission denied to delete node'); - - } - - /** - * Renames the node - * - * @throws DAV\Exception\Forbidden - * @param string $name The new name - * @return void - */ - public function setName($name) { - - throw new DAV\Exception\Forbidden('Permission denied to rename file'); - - } - - - /** - * Returns a list of alternative urls for a principal - * - * This can for example be an email address, or ldap url. - * - * @return array - */ - public function getAlternateUriSet() { - - return array(); - - } - - /** - * Returns the full principal url - * - * @return string - */ - public function getPrincipalUrl() { - - return $this->principalInfo['uri'] . '/' . $this->getName(); - - } - - /** - * Returns the list of group members - * - * If this principal is a group, this function should return - * all member principal uri's for the group. - * - * @return array - */ - public function getGroupMemberSet() { - - return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl()); - - } - - /** - * Returns the list of groups this principal is member of - * - * If this principal is a member of a (list of) groups, this function - * should return a list of principal uri's for it's members. - * - * @return array - */ - public function getGroupMembership() { - - return $this->principalBackend->getGroupMembership($this->getPrincipalUrl()); - - } - - /** - * Sets a list of group members - * - * If this principal is a group, this method sets all the group members. - * The list of members is always overwritten, never appended to. - * - * This method should throw an exception if the members could not be set. - * - * @param array $principals - * @return void - */ - public function setGroupMemberSet(array $principals) { - - $this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals); - - } - - /** - * Returns the displayname - * - * This should be a human readable name for the principal. - * If none is available, return the nodename. - * - * @return string - */ - public function getDisplayName() { - - return $this->getName(); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/ProxyWrite.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/ProxyWrite.php deleted file mode 100644 index 02cd81f70..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/ProxyWrite.php +++ /dev/null @@ -1,180 +0,0 @@ -principalInfo = $principalInfo; - $this->principalBackend = $principalBackend; - - } - - /** - * Returns this principals name. - * - * @return string - */ - public function getName() { - - return 'calendar-proxy-write'; - - } - - /** - * Returns the last modification time - * - * @return null - */ - public function getLastModified() { - - return null; - - } - - /** - * Deletes the current node - * - * @throws DAV\Exception\Forbidden - * @return void - */ - public function delete() { - - throw new DAV\Exception\Forbidden('Permission denied to delete node'); - - } - - /** - * Renames the node - * - * @throws DAV\Exception\Forbidden - * @param string $name The new name - * @return void - */ - public function setName($name) { - - throw new DAV\Exception\Forbidden('Permission denied to rename file'); - - } - - - /** - * Returns a list of alternative urls for a principal - * - * This can for example be an email address, or ldap url. - * - * @return array - */ - public function getAlternateUriSet() { - - return array(); - - } - - /** - * Returns the full principal url - * - * @return string - */ - public function getPrincipalUrl() { - - return $this->principalInfo['uri'] . '/' . $this->getName(); - - } - - /** - * Returns the list of group members - * - * If this principal is a group, this function should return - * all member principal uri's for the group. - * - * @return array - */ - public function getGroupMemberSet() { - - return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl()); - - } - - /** - * Returns the list of groups this principal is member of - * - * If this principal is a member of a (list of) groups, this function - * should return a list of principal uri's for it's members. - * - * @return array - */ - public function getGroupMembership() { - - return $this->principalBackend->getGroupMembership($this->getPrincipalUrl()); - - } - - /** - * Sets a list of group members - * - * If this principal is a group, this method sets all the group members. - * The list of members is always overwritten, never appended to. - * - * This method should throw an exception if the members could not be set. - * - * @param array $principals - * @return void - */ - public function setGroupMemberSet(array $principals) { - - $this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals); - - } - - /** - * Returns the displayname - * - * This should be a human readable name for the principal. - * If none is available, return the nodename. - * - * @return string - */ - public function getDisplayName() { - - return $this->getName(); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/User.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/User.php deleted file mode 100644 index 6f3ccb868..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Principal/User.php +++ /dev/null @@ -1,134 +0,0 @@ -principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/' . $name); - if (!$principal) { - throw new DAV\Exception\NotFound('Node with name ' . $name . ' was not found'); - } - if ($name === 'calendar-proxy-read') - return new ProxyRead($this->principalBackend, $this->principalProperties); - - if ($name === 'calendar-proxy-write') - return new ProxyWrite($this->principalBackend, $this->principalProperties); - - throw new DAV\Exception\NotFound('Node with name ' . $name . ' was not found'); - - } - - /** - * Returns an array with all the child nodes - * - * @return DAV\INode[] - */ - public function getChildren() { - - $r = array(); - if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-read')) { - $r[] = new ProxyRead($this->principalBackend, $this->principalProperties); - } - if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-write')) { - $r[] = new ProxyWrite($this->principalBackend, $this->principalProperties); - } - - return $r; - - } - - /** - * Returns whether or not the child node exists - * - * @param string $name - * @return bool - */ - public function childExists($name) { - - try { - $this->getChild($name); - return true; - } catch (DAV\Exception\NotFound $e) { - return false; - } - - } - - /** - * Returns a list of ACE's for this node. - * - * Each ACE has the following properties: - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are - * currently the only supported privileges - * * 'principal', a url to the principal who owns the node - * * 'protected' (optional), indicating that this ACE is not allowed to - * be updated. - * - * @return array - */ - public function getACL() { - - $acl = parent::getACL(); - $acl[] = array( - 'privilege' => '{DAV:}read', - 'principal' => $this->principalProperties['uri'] . '/calendar-proxy-read', - 'protected' => true, - ); - $acl[] = array( - 'privilege' => '{DAV:}read', - 'principal' => $this->principalProperties['uri'] . '/calendar-proxy-write', - 'protected' => true, - ); - return $acl; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Property/AllowedSharingModes.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Property/AllowedSharingModes.php deleted file mode 100644 index ca8312d40..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Property/AllowedSharingModes.php +++ /dev/null @@ -1,74 +0,0 @@ -canBeShared = $canBeShared; - $this->canBePublished = $canBePublished; - - } - - /** - * Serializes the property in a DOMDocument - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - public function serialize(DAV\Server $server, \DOMElement $node) { - - $doc = $node->ownerDocument; - if ($this->canBeShared) { - $xcomp = $doc->createElement('cs:can-be-shared'); - $node->appendChild($xcomp); - } - if ($this->canBePublished) { - $xcomp = $doc->createElement('cs:can-be-published'); - $node->appendChild($xcomp); - } - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Property/Invite.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Property/Invite.php deleted file mode 100644 index 86ef69e80..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Property/Invite.php +++ /dev/null @@ -1,227 +0,0 @@ -users = $users; - $this->organizer = $organizer; - - } - - /** - * Returns the list of users, as it was passed to the constructor. - * - * @return array - */ - public function getValue() { - - return $this->users; - - } - - /** - * Serializes the property in a DOMDocument - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - public function serialize(DAV\Server $server,\DOMElement $node) { - - $doc = $node->ownerDocument; - - if (!is_null($this->organizer)) { - - $xorganizer = $doc->createElement('cs:organizer'); - - $href = $doc->createElement('d:href'); - $href->appendChild($doc->createTextNode($this->organizer['href'])); - $xorganizer->appendChild($href); - - if (isset($this->organizer['commonName']) && $this->organizer['commonName']) { - $commonName = $doc->createElement('cs:common-name'); - $commonName->appendChild($doc->createTextNode($this->organizer['commonName'])); - $xorganizer->appendChild($commonName); - } - if (isset($this->organizer['firstName']) && $this->organizer['firstName']) { - $firstName = $doc->createElement('cs:first-name'); - $firstName->appendChild($doc->createTextNode($this->organizer['firstName'])); - $xorganizer->appendChild($firstName); - } - if (isset($this->organizer['lastName']) && $this->organizer['lastName']) { - $lastName = $doc->createElement('cs:last-name'); - $lastName->appendChild($doc->createTextNode($this->organizer['lastName'])); - $xorganizer->appendChild($lastName); - } - - $node->appendChild($xorganizer); - - - } - - foreach($this->users as $user) { - - $xuser = $doc->createElement('cs:user'); - - $href = $doc->createElement('d:href'); - $href->appendChild($doc->createTextNode($user['href'])); - $xuser->appendChild($href); - - if (isset($user['commonName']) && $user['commonName']) { - $commonName = $doc->createElement('cs:common-name'); - $commonName->appendChild($doc->createTextNode($user['commonName'])); - $xuser->appendChild($commonName); - } - - switch($user['status']) { - - case SharingPlugin::STATUS_ACCEPTED : - $status = $doc->createElement('cs:invite-accepted'); - $xuser->appendChild($status); - break; - case SharingPlugin::STATUS_DECLINED : - $status = $doc->createElement('cs:invite-declined'); - $xuser->appendChild($status); - break; - case SharingPlugin::STATUS_NORESPONSE : - $status = $doc->createElement('cs:invite-noresponse'); - $xuser->appendChild($status); - break; - case SharingPlugin::STATUS_INVALID : - $status = $doc->createElement('cs:invite-invalid'); - $xuser->appendChild($status); - break; - - } - - $xaccess = $doc->createElement('cs:access'); - - if ($user['readOnly']) { - $xaccess->appendChild( - $doc->createElement('cs:read') - ); - } else { - $xaccess->appendChild( - $doc->createElement('cs:read-write') - ); - } - $xuser->appendChild($xaccess); - - if (isset($user['summary']) && $user['summary']) { - $summary = $doc->createElement('cs:summary'); - $summary->appendChild($doc->createTextNode($user['summary'])); - $xuser->appendChild($summary); - } - - $node->appendChild($xuser); - - } - - - } - - /** - * Unserializes the property. - * - * This static method should return a an instance of this object. - * - * @param \DOMElement $prop - * @return DAV\IProperty - */ - static function unserialize(\DOMElement $prop) { - - $xpath = new \DOMXPath($prop->ownerDocument); - $xpath->registerNamespace('cs', CalDAV\Plugin::NS_CALENDARSERVER); - $xpath->registerNamespace('d', 'urn:DAV'); - - $users = array(); - - foreach($xpath->query('cs:user', $prop) as $user) { - - $status = null; - if ($xpath->evaluate('boolean(cs:invite-accepted)', $user)) { - $status = SharingPlugin::STATUS_ACCEPTED; - } elseif ($xpath->evaluate('boolean(cs:invite-declined)', $user)) { - $status = SharingPlugin::STATUS_DECLINED; - } elseif ($xpath->evaluate('boolean(cs:invite-noresponse)', $user)) { - $status = SharingPlugin::STATUS_NORESPONSE; - } elseif ($xpath->evaluate('boolean(cs:invite-invalid)', $user)) { - $status = SharingPlugin::STATUS_INVALID; - } else { - throw new DAV\Exception('Every cs:user property must have one of cs:invite-accepted, cs:invite-declined, cs:invite-noresponse or cs:invite-invalid'); - } - $users[] = array( - 'href' => $xpath->evaluate('string(d:href)', $user), - 'commonName' => $xpath->evaluate('string(cs:common-name)', $user), - 'readOnly' => $xpath->evaluate('boolean(cs:access/cs:read)', $user), - 'summary' => $xpath->evaluate('string(cs:summary)', $user), - 'status' => $status, - ); - - } - - return new self($users); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Property/ScheduleCalendarTransp.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Property/ScheduleCalendarTransp.php deleted file mode 100644 index 7f12d7585..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Property/ScheduleCalendarTransp.php +++ /dev/null @@ -1,102 +0,0 @@ -value = $value; - - } - - /** - * Returns the current value - * - * @return string - */ - public function getValue() { - - return $this->value; - - } - - /** - * Serializes the property in a DOMDocument - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - public function serialize(DAV\Server $server,\DOMElement $node) { - - $doc = $node->ownerDocument; - switch($this->value) { - case self::TRANSPARENT : - $xval = $doc->createElement('cal:transparent'); - break; - case self::OPAQUE : - $xval = $doc->createElement('cal:opaque'); - break; - } - - $node->appendChild($xval); - - } - - /** - * Unserializes the DOMElement back into a Property class. - * - * @param \DOMElement $node - * @return ScheduleCalendarTransp - */ - static function unserialize(\DOMElement $node) { - - $value = null; - foreach($node->childNodes as $childNode) { - switch(DAV\XMLUtil::toClarkNotation($childNode)) { - case '{' . CalDAV\Plugin::NS_CALDAV . '}opaque' : - $value = self::OPAQUE; - break; - case '{' . CalDAV\Plugin::NS_CALDAV . '}transparent' : - $value = self::TRANSPARENT; - break; - } - } - if (is_null($value)) - return null; - - return new self($value); - - } -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCalendarComponentSet.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCalendarComponentSet.php deleted file mode 100644 index 2538fa45d..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCalendarComponentSet.php +++ /dev/null @@ -1,88 +0,0 @@ -components = $components; - - } - - /** - * Returns the list of supported components - * - * @return array - */ - public function getValue() { - - return $this->components; - - } - - /** - * Serializes the property in a DOMDocument - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - public function serialize(DAV\Server $server,\DOMElement $node) { - - $doc = $node->ownerDocument; - foreach($this->components as $component) { - - $xcomp = $doc->createElement('cal:comp'); - $xcomp->setAttribute('name',$component); - $node->appendChild($xcomp); - - } - - } - - /** - * Unserializes the DOMElement back into a Property class. - * - * @param \DOMElement $node - * @return Property_SupportedCalendarComponentSet - */ - static function unserialize(\DOMElement $node) { - - $components = array(); - foreach($node->childNodes as $childNode) { - if (DAV\XMLUtil::toClarkNotation($childNode)==='{' . CalDAV\Plugin::NS_CALDAV . '}comp') { - $components[] = $childNode->getAttribute('name'); - } - } - return new self($components); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCalendarData.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCalendarData.php deleted file mode 100644 index efab80a91..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCalendarData.php +++ /dev/null @@ -1,40 +0,0 @@ -ownerDocument; - - $prefix = isset($server->xmlNamespaces[Plugin::NS_CALDAV])?$server->xmlNamespaces[Plugin::NS_CALDAV]:'cal'; - - $caldata = $doc->createElement($prefix . ':calendar-data'); - $caldata->setAttribute('content-type','text/calendar'); - $caldata->setAttribute('version','2.0'); - - $node->appendChild($caldata); - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCollationSet.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCollationSet.php deleted file mode 100644 index 63c37fe5a..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Property/SupportedCollationSet.php +++ /dev/null @@ -1,45 +0,0 @@ -ownerDocument; - - $prefix = $node->lookupPrefix('urn:ietf:params:xml:ns:caldav'); - if (!$prefix) $prefix = 'cal'; - - $node->appendChild( - $doc->createElement($prefix . ':supported-collation','i;ascii-casemap') - ); - $node->appendChild( - $doc->createElement($prefix . ':supported-collation','i;octet') - ); - $node->appendChild( - $doc->createElement($prefix . ':supported-collation','i;unicode-casemap') - ); - - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Schedule/IMip.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Schedule/IMip.php deleted file mode 100644 index b2b0d98b1..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Schedule/IMip.php +++ /dev/null @@ -1,111 +0,0 @@ -senderEmail = $senderEmail; - - } - - /** - * Sends one or more iTip messages through email. - * - * @param string $originator Originator Email - * @param array $recipients Array of email addresses - * @param VObject\Component $vObject - * @param string $principal Principal Url of the originator - * @return void - */ - public function sendMessage($originator, array $recipients, VObject\Component $vObject, $principal) { - - foreach($recipients as $recipient) { - - $to = $recipient; - $replyTo = $originator; - $subject = 'SabreDAV iTIP message'; - - switch(strtoupper($vObject->METHOD)) { - case 'REPLY' : - $subject = 'Response for: ' . $vObject->VEVENT->SUMMARY; - break; - case 'REQUEST' : - $subject = 'Invitation for: ' .$vObject->VEVENT->SUMMARY; - break; - case 'CANCEL' : - $subject = 'Cancelled event: ' . $vObject->VEVENT->SUMMARY; - break; - } - - $headers = array(); - $headers[] = 'Reply-To: ' . $replyTo; - $headers[] = 'From: ' . $this->senderEmail; - $headers[] = 'Content-Type: text/calendar; method=' . (string)$vObject->method . '; charset=utf-8'; - if (DAV\Server::$exposeVersion) { - $headers[] = 'X-Sabre-Version: ' . DAV\Version::VERSION . '-' . DAV\Version::STABILITY; - } - - $vcalBody = $vObject->serialize(); - - $this->mail($to, $subject, $vcalBody, $headers); - - } - - } - - // @codeCoverageIgnoreStart - // This is deemed untestable in a reasonable manner - - /** - * This function is reponsible for sending the actual email. - * - * @param string $to Recipient email address - * @param string $subject Subject of the email - * @param string $body iCalendar body - * @param array $headers List of headers - * @return void - */ - protected function mail($to, $subject, $body, array $headers) { - - - mail($to, $subject, $body, implode("\r\n", $headers)); - - } - - // @codeCoverageIgnoreEnd - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Schedule/IOutbox.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Schedule/IOutbox.php deleted file mode 100644 index 7341eaa85..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Schedule/IOutbox.php +++ /dev/null @@ -1,16 +0,0 @@ -principalUri = $principalUri; - - } - - /** - * Returns the name of the node. - * - * This is used to generate the url. - * - * @return string - */ - public function getName() { - - return 'outbox'; - - } - - /** - * Returns an array with all the child nodes - * - * @return \Sabre\DAV\INode[] - */ - public function getChildren() { - - return array(); - - } - - /** - * Returns the owner principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - public function getOwner() { - - return $this->principalUri; - - } - - /** - * Returns a group principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - public function getGroup() { - - return null; - - } - - /** - * Returns a list of ACE's for this node. - * - * Each ACE has the following properties: - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are - * currently the only supported privileges - * * 'principal', a url to the principal who owns the node - * * 'protected' (optional), indicating that this ACE is not allowed to - * be updated. - * - * @return array - */ - public function getACL() { - - return array( - array( - 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy', - 'principal' => $this->getOwner(), - 'protected' => true, - ), - array( - 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent', - 'principal' => $this->getOwner(), - 'protected' => true, - ), - array( - 'privilege' => '{DAV:}read', - 'principal' => $this->getOwner(), - 'protected' => true, - ), - ); - - } - - /** - * Updates the ACL - * - * This method will receive a list of new ACE's. - * - * @param array $acl - * @return void - */ - public function setACL(array $acl) { - - throw new DAV\Exception\MethodNotAllowed('You\'re not allowed to update the ACL'); - - } - - /** - * Returns the list of supported privileges for this node. - * - * The returned data structure is a list of nested privileges. - * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple - * standard structure. - * - * If null is returned from this method, the default privilege set is used, - * which is fine for most common usecases. - * - * @return array|null - */ - public function getSupportedPrivilegeSet() { - - $default = DAVACL\Plugin::getDefaultSupportedPrivilegeSet(); - $default['aggregates'][] = array( - 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy', - ); - $default['aggregates'][] = array( - 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent', - ); - - return $default; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/ShareableCalendar.php b/vendor/sabre/dav/lib/Sabre/CalDAV/ShareableCalendar.php deleted file mode 100644 index cabf7eb95..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/ShareableCalendar.php +++ /dev/null @@ -1,72 +0,0 @@ -caldavBackend->updateShares($this->calendarInfo['id'], $add, $remove); - - } - - /** - * Returns the list of people whom this calendar is shared with. - * - * Every element in this array should have the following properties: - * * href - Often a mailto: address - * * commonName - Optional, for example a first + last name - * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants. - * * readOnly - boolean - * * summary - Optional, a description for the share - * - * @return array - */ - public function getShares() { - - return $this->caldavBackend->getShares($this->calendarInfo['id']); - - } - - /** - * Marks this calendar as published. - * - * Publishing a calendar should automatically create a read-only, public, - * subscribable calendar. - * - * @param bool $value - * @return void - */ - public function setPublishStatus($value) { - - $this->caldavBackend->setPublishStatus($this->calendarInfo['id'], $value); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/SharedCalendar.php b/vendor/sabre/dav/lib/Sabre/CalDAV/SharedCalendar.php deleted file mode 100644 index 79eda43ab..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/SharedCalendar.php +++ /dev/null @@ -1,116 +0,0 @@ -calendarInfo['{http://calendarserver.org/ns/}shared-url']; - - } - - /** - * Returns the owner principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - public function getOwner() { - - return $this->calendarInfo['{http://sabredav.org/ns}owner-principal']; - - } - - /** - * Returns a list of ACE's for this node. - * - * Each ACE has the following properties: - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are - * currently the only supported privileges - * * 'principal', a url to the principal who owns the node - * * 'protected' (optional), indicating that this ACE is not allowed to - * be updated. - * - * @return array - */ - public function getACL() { - - // The top-level ACL only contains access information for the true - // owner of the calendar, so we need to add the information for the - // sharee. - $acl = parent::getACL(); - $acl[] = array( - 'privilege' => '{DAV:}read', - 'principal' => $this->calendarInfo['principaluri'], - 'protected' => true, - ); - if (!$this->calendarInfo['{http://sabredav.org/ns}read-only']) { - $acl[] = array( - 'privilege' => '{DAV:}write', - 'principal' => $this->calendarInfo['principaluri'], - 'protected' => true, - ); - } - return $acl; - - } - - /** - * Returns the list of people whom this calendar is shared with. - * - * Every element in this array should have the following properties: - * * href - Often a mailto: address - * * commonName - Optional, for example a first + last name - * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants. - * * readOnly - boolean - * * summary - Optional, a description for the share - * - * @return array - */ - public function getShares() { - - return $this->caldavBackend->getShares($this->calendarInfo['id']); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/SharingPlugin.php b/vendor/sabre/dav/lib/Sabre/CalDAV/SharingPlugin.php deleted file mode 100644 index e869cb278..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/SharingPlugin.php +++ /dev/null @@ -1,526 +0,0 @@ -server = $server; - $server->resourceTypeMapping['Sabre\\CalDAV\\ISharedCalendar'] = '{' . Plugin::NS_CALENDARSERVER . '}shared'; - - array_push( - $this->server->protectedProperties, - '{' . Plugin::NS_CALENDARSERVER . '}invite', - '{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes', - '{' . Plugin::NS_CALENDARSERVER . '}shared-url' - ); - - $this->server->subscribeEvent('beforeGetProperties', array($this, 'beforeGetProperties')); - $this->server->subscribeEvent('afterGetProperties', array($this, 'afterGetProperties')); - $this->server->subscribeEvent('updateProperties', array($this, 'updateProperties')); - $this->server->subscribeEvent('unknownMethod', array($this,'unknownMethod')); - - } - - /** - * This event is triggered when properties are requested for a certain - * node. - * - * This allows us to inject any properties early. - * - * @param string $path - * @param DAV\INode $node - * @param array $requestedProperties - * @param array $returnedProperties - * @return void - */ - public function beforeGetProperties($path, DAV\INode $node, &$requestedProperties, &$returnedProperties) { - - if ($node instanceof IShareableCalendar) { - if (($index = array_search('{' . Plugin::NS_CALENDARSERVER . '}invite', $requestedProperties))!==false) { - - unset($requestedProperties[$index]); - $returnedProperties[200]['{' . Plugin::NS_CALENDARSERVER . '}invite'] = - new Property\Invite( - $node->getShares() - ); - - } - - } - - if ($node instanceof ISharedCalendar) { - - if (($index = array_search('{' . Plugin::NS_CALENDARSERVER . '}shared-url', $requestedProperties))!==false) { - - unset($requestedProperties[$index]); - $returnedProperties[200]['{' . Plugin::NS_CALENDARSERVER . '}shared-url'] = - new DAV\Property\Href( - $node->getSharedUrl() - ); - - } - // The 'invite' property is slightly different for the 'shared' - // instance of the calendar, as it also contains the owner - // information. - if (($index = array_search('{' . Plugin::NS_CALENDARSERVER . '}invite', $requestedProperties))!==false) { - - unset($requestedProperties[$index]); - - // Fetching owner information - $props = $this->server->getPropertiesForPath($node->getOwner(), array( - '{http://sabredav.org/ns}email-address', - '{DAV:}displayname', - ), 1); - - $ownerInfo = array( - 'href' => $node->getOwner(), - ); - - if (isset($props[0][200])) { - - // We're mapping the internal webdav properties to the - // elements caldav-sharing expects. - if (isset($props[0][200]['{http://sabredav.org/ns}email-address'])) { - $ownerInfo['href'] = 'mailto:' . $props[0][200]['{http://sabredav.org/ns}email-address']; - } - if (isset($props[0][200]['{DAV:}displayname'])) { - $ownerInfo['commonName'] = $props[0][200]['{DAV:}displayname']; - } - - } - - $returnedProperties[200]['{' . Plugin::NS_CALENDARSERVER . '}invite'] = - new Property\Invite( - $node->getShares(), - $ownerInfo - ); - - } - - - } - - } - - /** - * This method is triggered *after* all properties have been retrieved. - * This allows us to inject the correct resourcetype for calendars that - * have been shared. - * - * @param string $path - * @param array $properties - * @param DAV\INode $node - * @return void - */ - public function afterGetProperties($path, &$properties, DAV\INode $node) { - - if ($node instanceof IShareableCalendar) { - if (isset($properties[200]['{DAV:}resourcetype'])) { - if (count($node->getShares())>0) { - $properties[200]['{DAV:}resourcetype']->add( - '{' . Plugin::NS_CALENDARSERVER . '}shared-owner' - ); - } - } - $propName = '{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes'; - if (array_key_exists($propName, $properties[404])) { - unset($properties[404][$propName]); - $properties[200][$propName] = new Property\AllowedSharingModes(true,false); - } - - } - - } - - /** - * This method is trigged when a user attempts to update a node's - * properties. - * - * A previous draft of the sharing spec stated that it was possible to use - * PROPPATCH to remove 'shared-owner' from the resourcetype, thus unsharing - * the calendar. - * - * Even though this is no longer in the current spec, we keep this around - * because OS X 10.7 may still make use of this feature. - * - * @param array $mutations - * @param array $result - * @param DAV\INode $node - * @return void - */ - public function updateProperties(array &$mutations, array &$result, DAV\INode $node) { - - if (!$node instanceof IShareableCalendar) - return; - - if (!isset($mutations['{DAV:}resourcetype'])) { - return; - } - - // Only doing something if shared-owner is indeed not in the list. - if($mutations['{DAV:}resourcetype']->is('{' . Plugin::NS_CALENDARSERVER . '}shared-owner')) return; - - $shares = $node->getShares(); - $remove = array(); - foreach($shares as $share) { - $remove[] = $share['href']; - } - $node->updateShares(array(), $remove); - - // We're marking this update as 200 OK - $result[200]['{DAV:}resourcetype'] = null; - - // Removing it from the mutations list - unset($mutations['{DAV:}resourcetype']); - - } - - /** - * This event is triggered when the server didn't know how to handle a - * certain request. - * - * We intercept this to handle POST requests on calendars. - * - * @param string $method - * @param string $uri - * @return null|bool - */ - public function unknownMethod($method, $uri) { - - if ($method!=='POST') { - return; - } - - // Only handling xml - $contentType = $this->server->httpRequest->getHeader('Content-Type'); - if (strpos($contentType,'application/xml')===false && strpos($contentType,'text/xml')===false) - return; - - // Making sure the node exists - try { - $node = $this->server->tree->getNodeForPath($uri); - } catch (DAV\Exception\NotFound $e) { - return; - } - - $requestBody = $this->server->httpRequest->getBody(true); - - // If this request handler could not deal with this POST request, it - // will return 'null' and other plugins get a chance to handle the - // request. - // - // However, we already requested the full body. This is a problem, - // because a body can only be read once. This is why we preemptively - // re-populated the request body with the existing data. - $this->server->httpRequest->setBody($requestBody); - - $dom = DAV\XMLUtil::loadDOMDocument($requestBody); - - $documentType = DAV\XMLUtil::toClarkNotation($dom->firstChild); - - switch($documentType) { - - // Dealing with the 'share' document, which modified invitees on a - // calendar. - case '{' . Plugin::NS_CALENDARSERVER . '}share' : - - // We can only deal with IShareableCalendar objects - if (!$node instanceof IShareableCalendar) { - return; - } - - // Getting ACL info - $acl = $this->server->getPlugin('acl'); - - // If there's no ACL support, we allow everything - if ($acl) { - $acl->checkPrivileges($uri, '{DAV:}write'); - } - - $mutations = $this->parseShareRequest($dom); - - $node->updateShares($mutations[0], $mutations[1]); - - $this->server->httpResponse->sendStatus(200); - // Adding this because sending a response body may cause issues, - // and I wanted some type of indicator the response was handled. - $this->server->httpResponse->setHeader('X-Sabre-Status', 'everything-went-well'); - - // Breaking the event chain - return false; - - // The invite-reply document is sent when the user replies to an - // invitation of a calendar share. - case '{'. Plugin::NS_CALENDARSERVER.'}invite-reply' : - - // This only works on the calendar-home-root node. - if (!$node instanceof UserCalendars) { - return; - } - - // Getting ACL info - $acl = $this->server->getPlugin('acl'); - - // If there's no ACL support, we allow everything - if ($acl) { - $acl->checkPrivileges($uri, '{DAV:}write'); - } - - $message = $this->parseInviteReplyRequest($dom); - - $url = $node->shareReply( - $message['href'], - $message['status'], - $message['calendarUri'], - $message['inReplyTo'], - $message['summary'] - ); - - $this->server->httpResponse->sendStatus(200); - // Adding this because sending a response body may cause issues, - // and I wanted some type of indicator the response was handled. - $this->server->httpResponse->setHeader('X-Sabre-Status', 'everything-went-well'); - - if ($url) { - $dom = new \DOMDocument('1.0', 'UTF-8'); - $dom->formatOutput = true; - - $root = $dom->createElement('cs:shared-as'); - foreach($this->server->xmlNamespaces as $namespace => $prefix) { - $root->setAttribute('xmlns:' . $prefix, $namespace); - } - - $dom->appendChild($root); - $href = new DAV\Property\Href($url); - - $href->serialize($this->server, $root); - $this->server->httpResponse->setHeader('Content-Type','application/xml'); - $this->server->httpResponse->sendBody($dom->saveXML()); - - } - - // Breaking the event chain - return false; - - case '{' . Plugin::NS_CALENDARSERVER . '}publish-calendar' : - - // We can only deal with IShareableCalendar objects - if (!$node instanceof IShareableCalendar) { - return; - } - - // Getting ACL info - $acl = $this->server->getPlugin('acl'); - - // If there's no ACL support, we allow everything - if ($acl) { - $acl->checkPrivileges($uri, '{DAV:}write'); - } - - $node->setPublishStatus(true); - - // iCloud sends back the 202, so we will too. - $this->server->httpResponse->sendStatus(202); - - // Adding this because sending a response body may cause issues, - // and I wanted some type of indicator the response was handled. - $this->server->httpResponse->setHeader('X-Sabre-Status', 'everything-went-well'); - - // Breaking the event chain - return false; - - case '{' . Plugin::NS_CALENDARSERVER . '}unpublish-calendar' : - - // We can only deal with IShareableCalendar objects - if (!$node instanceof IShareableCalendar) { - return; - } - - // Getting ACL info - $acl = $this->server->getPlugin('acl'); - - // If there's no ACL support, we allow everything - if ($acl) { - $acl->checkPrivileges($uri, '{DAV:}write'); - } - - $node->setPublishStatus(false); - - $this->server->httpResponse->sendStatus(200); - - // Adding this because sending a response body may cause issues, - // and I wanted some type of indicator the response was handled. - $this->server->httpResponse->setHeader('X-Sabre-Status', 'everything-went-well'); - - // Breaking the event chain - return false; - - } - - - - } - - /** - * Parses the 'share' POST request. - * - * This method returns an array, containing two arrays. - * The first array is a list of new sharees. Every element is a struct - * containing a: - * * href element. (usually a mailto: address) - * * commonName element (often a first and lastname, but can also be - * false) - * * readOnly (true or false) - * * summary (A description of the share, can also be false) - * - * The second array is a list of sharees that are to be removed. This is - * just a simple array with 'hrefs'. - * - * @param \DOMDocument $dom - * @return array - */ - protected function parseShareRequest(\DOMDocument $dom) { - - $xpath = new \DOMXPath($dom); - $xpath->registerNamespace('cs', Plugin::NS_CALENDARSERVER); - $xpath->registerNamespace('d', 'urn:DAV'); - - $set = array(); - $elems = $xpath->query('cs:set'); - - for($i=0; $i < $elems->length; $i++) { - - $xset = $elems->item($i); - $set[] = array( - 'href' => $xpath->evaluate('string(d:href)', $xset), - 'commonName' => $xpath->evaluate('string(cs:common-name)', $xset), - 'summary' => $xpath->evaluate('string(cs:summary)', $xset), - 'readOnly' => $xpath->evaluate('boolean(cs:read)', $xset)!==false - ); - - } - - $remove = array(); - $elems = $xpath->query('cs:remove'); - - for($i=0; $i < $elems->length; $i++) { - - $xremove = $elems->item($i); - $remove[] = $xpath->evaluate('string(d:href)', $xremove); - - } - - return array($set, $remove); - - } - - /** - * Parses the 'invite-reply' POST request. - * - * This method returns an array, containing the following properties: - * * href - The sharee who is replying - * * status - One of the self::STATUS_* constants - * * calendarUri - The url of the shared calendar - * * inReplyTo - The unique id of the share invitation. - * * summary - Optional description of the reply. - * - * @param \DOMDocument $dom - * @return array - */ - protected function parseInviteReplyRequest(\DOMDocument $dom) { - - $xpath = new \DOMXPath($dom); - $xpath->registerNamespace('cs', Plugin::NS_CALENDARSERVER); - $xpath->registerNamespace('d', 'urn:DAV'); - - $hostHref = $xpath->evaluate('string(cs:hosturl/d:href)'); - if (!$hostHref) { - throw new DAV\Exception\BadRequest('The {' . Plugin::NS_CALENDARSERVER . '}hosturl/{DAV:}href element is required'); - } - - return array( - 'href' => $xpath->evaluate('string(d:href)'), - 'calendarUri' => $this->server->calculateUri($hostHref), - 'inReplyTo' => $xpath->evaluate('string(cs:in-reply-to)'), - 'summary' => $xpath->evaluate('string(cs:summary)'), - 'status' => $xpath->evaluate('boolean(cs:invite-accepted)')?self::STATUS_ACCEPTED:self::STATUS_DECLINED - ); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/UserCalendars.php b/vendor/sabre/dav/lib/Sabre/CalDAV/UserCalendars.php deleted file mode 100644 index 6e700eb04..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/UserCalendars.php +++ /dev/null @@ -1,342 +0,0 @@ -caldavBackend = $caldavBackend; - $this->principalInfo = $principalInfo; - - } - - /** - * Returns the name of this object - * - * @return string - */ - public function getName() { - - list(,$name) = DAV\URLUtil::splitPath($this->principalInfo['uri']); - return $name; - - } - - /** - * Updates the name of this object - * - * @param string $name - * @return void - */ - public function setName($name) { - - throw new DAV\Exception\Forbidden(); - - } - - /** - * Deletes this object - * - * @return void - */ - public function delete() { - - throw new DAV\Exception\Forbidden(); - - } - - /** - * Returns the last modification date - * - * @return int - */ - public function getLastModified() { - - return null; - - } - - /** - * Creates a new file under this object. - * - * This is currently not allowed - * - * @param string $filename - * @param resource $data - * @return void - */ - public function createFile($filename, $data=null) { - - throw new DAV\Exception\MethodNotAllowed('Creating new files in this collection is not supported'); - - } - - /** - * Creates a new directory under this object. - * - * This is currently not allowed. - * - * @param string $filename - * @return void - */ - public function createDirectory($filename) { - - throw new DAV\Exception\MethodNotAllowed('Creating new collections in this collection is not supported'); - - } - - /** - * Returns a single calendar, by name - * - * @param string $name - * @todo needs optimizing - * @return Calendar - */ - public function getChild($name) { - - foreach($this->getChildren() as $child) { - if ($name==$child->getName()) - return $child; - - } - throw new DAV\Exception\NotFound('Calendar with name \'' . $name . '\' could not be found'); - - } - - /** - * Checks if a calendar exists. - * - * @param string $name - * @todo needs optimizing - * @return bool - */ - public function childExists($name) { - - foreach($this->getChildren() as $child) { - if ($name==$child->getName()) - return true; - - } - return false; - - } - - /** - * Returns a list of calendars - * - * @return array - */ - public function getChildren() { - - $calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']); - $objs = array(); - foreach($calendars as $calendar) { - if ($this->caldavBackend instanceof Backend\SharingSupport) { - if (isset($calendar['{http://calendarserver.org/ns/}shared-url'])) { - $objs[] = new SharedCalendar($this->caldavBackend, $calendar); - } else { - $objs[] = new ShareableCalendar($this->caldavBackend, $calendar); - } - } else { - $objs[] = new Calendar($this->caldavBackend, $calendar); - } - } - $objs[] = new Schedule\Outbox($this->principalInfo['uri']); - - // We're adding a notifications node, if it's supported by the backend. - if ($this->caldavBackend instanceof Backend\NotificationSupport) { - $objs[] = new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']); - } - return $objs; - - } - - /** - * Creates a new calendar - * - * @param string $name - * @param array $resourceType - * @param array $properties - * @return void - */ - public function createExtendedCollection($name, array $resourceType, array $properties) { - - $isCalendar = false; - foreach($resourceType as $rt) { - switch ($rt) { - case '{DAV:}collection' : - case '{http://calendarserver.org/ns/}shared-owner' : - // ignore - break; - case '{urn:ietf:params:xml:ns:caldav}calendar' : - $isCalendar = true; - break; - default : - throw new DAV\Exception\InvalidResourceType('Unknown resourceType: ' . $rt); - } - } - if (!$isCalendar) { - throw new DAV\Exception\InvalidResourceType('You can only create calendars in this collection'); - } - $this->caldavBackend->createCalendar($this->principalInfo['uri'], $name, $properties); - - } - - /** - * Returns the owner principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - public function getOwner() { - - return $this->principalInfo['uri']; - - } - - /** - * Returns a group principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - public function getGroup() { - - return null; - - } - - /** - * Returns a list of ACE's for this node. - * - * Each ACE has the following properties: - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are - * currently the only supported privileges - * * 'principal', a url to the principal who owns the node - * * 'protected' (optional), indicating that this ACE is not allowed to - * be updated. - * - * @return array - */ - public function getACL() { - - return array( - array( - 'privilege' => '{DAV:}read', - 'principal' => $this->principalInfo['uri'], - 'protected' => true, - ), - array( - 'privilege' => '{DAV:}write', - 'principal' => $this->principalInfo['uri'], - 'protected' => true, - ), - array( - 'privilege' => '{DAV:}read', - 'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write', - 'protected' => true, - ), - array( - 'privilege' => '{DAV:}write', - 'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write', - 'protected' => true, - ), - array( - 'privilege' => '{DAV:}read', - 'principal' => $this->principalInfo['uri'] . '/calendar-proxy-read', - 'protected' => true, - ), - - ); - - } - - /** - * Updates the ACL - * - * This method will receive a list of new ACE's. - * - * @param array $acl - * @return void - */ - public function setACL(array $acl) { - - throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported'); - - } - - /** - * Returns the list of supported privileges for this node. - * - * The returned data structure is a list of nested privileges. - * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple - * standard structure. - * - * If null is returned from this method, the default privilege set is used, - * which is fine for most common usecases. - * - * @return array|null - */ - public function getSupportedPrivilegeSet() { - - return null; - - } - - /** - * This method is called when a user replied to a request to share. - * - * This method should return the url of the newly created calendar if the - * share was accepted. - * - * @param string href The sharee who is replying (often a mailto: address) - * @param int status One of the SharingPlugin::STATUS_* constants - * @param string $calendarUri The url to the calendar thats being shared - * @param string $inReplyTo The unique id this message is a response to - * @param string $summary A description of the reply - * @return null|string - */ - public function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null) { - - if (!$this->caldavBackend instanceof Backend\SharingSupport) { - throw new DAV\Exception\NotImplemented('Sharing support is not implemented by this backend.'); - } - - return $this->caldavBackend->shareReply($href, $status, $calendarUri, $inReplyTo, $summary); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CalDAV/Version.php b/vendor/sabre/dav/lib/Sabre/CalDAV/Version.php deleted file mode 100644 index f30fc20ea..000000000 --- a/vendor/sabre/dav/lib/Sabre/CalDAV/Version.php +++ /dev/null @@ -1,24 +0,0 @@ -carddavBackend = $carddavBackend; - $this->addressBookInfo = $addressBookInfo; - - } - - /** - * Returns the name of the addressbook - * - * @return string - */ - public function getName() { - - return $this->addressBookInfo['uri']; - - } - - /** - * Returns a card - * - * @param string $name - * @return \ICard - */ - public function getChild($name) { - - $obj = $this->carddavBackend->getCard($this->addressBookInfo['id'],$name); - if (!$obj) throw new DAV\Exception\NotFound('Card not found'); - return new Card($this->carddavBackend,$this->addressBookInfo,$obj); - - } - - /** - * Returns the full list of cards - * - * @return array - */ - public function getChildren() { - - $objs = $this->carddavBackend->getCards($this->addressBookInfo['id']); - $children = array(); - foreach($objs as $obj) { - $children[] = new Card($this->carddavBackend,$this->addressBookInfo,$obj); - } - return $children; - - } - - /** - * Creates a new directory - * - * We actually block this, as subdirectories are not allowed in addressbooks. - * - * @param string $name - * @return void - */ - public function createDirectory($name) { - - throw new DAV\Exception\MethodNotAllowed('Creating collections in addressbooks is not allowed'); - - } - - /** - * Creates a new file - * - * The contents of the new file must be a valid VCARD. - * - * This method may return an ETag. - * - * @param string $name - * @param resource $vcardData - * @return string|null - */ - public function createFile($name,$vcardData = null) { - - if (is_resource($vcardData)) { - $vcardData = stream_get_contents($vcardData); - } - // Converting to UTF-8, if needed - $vcardData = DAV\StringUtil::ensureUTF8($vcardData); - - return $this->carddavBackend->createCard($this->addressBookInfo['id'],$name,$vcardData); - - } - - /** - * Deletes the entire addressbook. - * - * @return void - */ - public function delete() { - - $this->carddavBackend->deleteAddressBook($this->addressBookInfo['id']); - - } - - /** - * Renames the addressbook - * - * @param string $newName - * @return void - */ - public function setName($newName) { - - throw new DAV\Exception\MethodNotAllowed('Renaming addressbooks is not yet supported'); - - } - - /** - * Returns the last modification date as a unix timestamp. - * - * @return void - */ - public function getLastModified() { - - return null; - - } - - /** - * Updates properties on this node, - * - * The properties array uses the propertyName in clark-notation as key, - * and the array value for the property value. In the case a property - * should be deleted, the property value will be null. - * - * This method must be atomic. If one property cannot be changed, the - * entire operation must fail. - * - * If the operation was successful, true can be returned. - * If the operation failed, false can be returned. - * - * Deletion of a non-existent property is always successful. - * - * Lastly, it is optional to return detailed information about any - * failures. In this case an array should be returned with the following - * structure: - * - * array( - * 403 => array( - * '{DAV:}displayname' => null, - * ), - * 424 => array( - * '{DAV:}owner' => null, - * ) - * ) - * - * In this example it was forbidden to update {DAV:}displayname. - * (403 Forbidden), which in turn also caused {DAV:}owner to fail - * (424 Failed Dependency) because the request needs to be atomic. - * - * @param array $mutations - * @return bool|array - */ - public function updateProperties($mutations) { - - return $this->carddavBackend->updateAddressBook($this->addressBookInfo['id'], $mutations); - - } - - /** - * Returns a list of properties for this nodes. - * - * The properties list is a list of propertynames the client requested, - * encoded in clark-notation {xmlnamespace}tagname - * - * If the array is empty, it means 'all properties' were requested. - * - * @param array $properties - * @return array - */ - public function getProperties($properties) { - - $response = array(); - foreach($properties as $propertyName) { - - if (isset($this->addressBookInfo[$propertyName])) { - - $response[$propertyName] = $this->addressBookInfo[$propertyName]; - - } - - } - - return $response; - - } - - /** - * Returns the owner principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - public function getOwner() { - - return $this->addressBookInfo['principaluri']; - - } - - /** - * Returns a group principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - public function getGroup() { - - return null; - - } - - /** - * Returns a list of ACE's for this node. - * - * Each ACE has the following properties: - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are - * currently the only supported privileges - * * 'principal', a url to the principal who owns the node - * * 'protected' (optional), indicating that this ACE is not allowed to - * be updated. - * - * @return array - */ - public function getACL() { - - return array( - array( - 'privilege' => '{DAV:}read', - 'principal' => $this->addressBookInfo['principaluri'], - 'protected' => true, - ), - array( - 'privilege' => '{DAV:}write', - 'principal' => $this->addressBookInfo['principaluri'], - 'protected' => true, - ), - - ); - - } - - /** - * Updates the ACL - * - * This method will receive a list of new ACE's. - * - * @param array $acl - * @return void - */ - public function setACL(array $acl) { - - throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported'); - - } - - /** - * Returns the list of supported privileges for this node. - * - * The returned data structure is a list of nested privileges. - * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple - * standard structure. - * - * If null is returned from this method, the default privilege set is used, - * which is fine for most common usecases. - * - * @return array|null - */ - public function getSupportedPrivilegeSet() { - - return null; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CardDAV/AddressBookQueryParser.php b/vendor/sabre/dav/lib/Sabre/CardDAV/AddressBookQueryParser.php deleted file mode 100644 index 3277d98b0..000000000 --- a/vendor/sabre/dav/lib/Sabre/CardDAV/AddressBookQueryParser.php +++ /dev/null @@ -1,221 +0,0 @@ -dom = $dom; - - $this->xpath = new \DOMXPath($dom); - $this->xpath->registerNameSpace('card',Plugin::NS_CARDDAV); - - } - - /** - * Parses the request. - * - * @return void - */ - public function parse() { - - $filterNode = null; - - $limit = $this->xpath->evaluate('number(/card:addressbook-query/card:limit/card:nresults)'); - if (is_nan($limit)) $limit = null; - - $filter = $this->xpath->query('/card:addressbook-query/card:filter'); - - // According to the CardDAV spec there needs to be exactly 1 filter - // element. However, KDE 4.8.2 contains a bug that will encode 0 filter - // elements, so this is a workaround for that. - // - // See: https://bugs.kde.org/show_bug.cgi?id=300047 - if ($filter->length === 0) { - $test = null; - $filter = null; - } elseif ($filter->length === 1) { - $filter = $filter->item(0); - $test = $this->xpath->evaluate('string(@test)', $filter); - } else { - throw new DAV\Exception\BadRequest('Only one filter element is allowed'); - } - - if (!$test) $test = self::TEST_ANYOF; - if ($test !== self::TEST_ANYOF && $test !== self::TEST_ALLOF) { - throw new DAV\Exception\BadRequest('The test attribute must either hold "anyof" or "allof"'); - } - - $propFilters = array(); - - $propFilterNodes = $this->xpath->query('card:prop-filter', $filter); - for($ii=0; $ii < $propFilterNodes->length; $ii++) { - - $propFilters[] = $this->parsePropFilterNode($propFilterNodes->item($ii)); - - - } - - $this->filters = $propFilters; - $this->limit = $limit; - $this->requestedProperties = array_keys(DAV\XMLUtil::parseProperties($this->dom->firstChild)); - $this->test = $test; - - } - - /** - * Parses the prop-filter xml element - * - * @param \DOMElement $propFilterNode - * @return array - */ - protected function parsePropFilterNode(\DOMElement $propFilterNode) { - - $propFilter = array(); - $propFilter['name'] = $propFilterNode->getAttribute('name'); - $propFilter['test'] = $propFilterNode->getAttribute('test'); - if (!$propFilter['test']) $propFilter['test'] = 'anyof'; - - $propFilter['is-not-defined'] = $this->xpath->query('card:is-not-defined', $propFilterNode)->length>0; - - $paramFilterNodes = $this->xpath->query('card:param-filter', $propFilterNode); - - $propFilter['param-filters'] = array(); - - - for($ii=0;$ii<$paramFilterNodes->length;$ii++) { - - $propFilter['param-filters'][] = $this->parseParamFilterNode($paramFilterNodes->item($ii)); - - } - $propFilter['text-matches'] = array(); - $textMatchNodes = $this->xpath->query('card:text-match', $propFilterNode); - - for($ii=0;$ii<$textMatchNodes->length;$ii++) { - - $propFilter['text-matches'][] = $this->parseTextMatchNode($textMatchNodes->item($ii)); - - } - - return $propFilter; - - } - - /** - * Parses the param-filter element - * - * @param \DOMElement $paramFilterNode - * @return array - */ - public function parseParamFilterNode(\DOMElement $paramFilterNode) { - - $paramFilter = array(); - $paramFilter['name'] = $paramFilterNode->getAttribute('name'); - $paramFilter['is-not-defined'] = $this->xpath->query('card:is-not-defined', $paramFilterNode)->length>0; - $paramFilter['text-match'] = null; - - $textMatch = $this->xpath->query('card:text-match', $paramFilterNode); - if ($textMatch->length>0) { - $paramFilter['text-match'] = $this->parseTextMatchNode($textMatch->item(0)); - } - - return $paramFilter; - - } - - /** - * Text match - * - * @param \DOMElement $textMatchNode - * @return array - */ - public function parseTextMatchNode(\DOMElement $textMatchNode) { - - $matchType = $textMatchNode->getAttribute('match-type'); - if (!$matchType) $matchType = 'contains'; - - if (!in_array($matchType, array('contains', 'equals', 'starts-with', 'ends-with'))) { - throw new DAV\Exception\BadRequest('Unknown match-type: ' . $matchType); - } - - $negateCondition = $textMatchNode->getAttribute('negate-condition'); - $negateCondition = $negateCondition==='yes'; - $collation = $textMatchNode->getAttribute('collation'); - if (!$collation) $collation = 'i;unicode-casemap'; - - return array( - 'negate-condition' => $negateCondition, - 'collation' => $collation, - 'match-type' => $matchType, - 'value' => $textMatchNode->nodeValue - ); - - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CardDAV/AddressBookRoot.php b/vendor/sabre/dav/lib/Sabre/CardDAV/AddressBookRoot.php deleted file mode 100644 index 789abbc5d..000000000 --- a/vendor/sabre/dav/lib/Sabre/CardDAV/AddressBookRoot.php +++ /dev/null @@ -1,80 +0,0 @@ -carddavBackend = $carddavBackend; - parent::__construct($principalBackend, $principalPrefix); - - } - - /** - * Returns the name of the node - * - * @return string - */ - public function getName() { - - return Plugin::ADDRESSBOOK_ROOT; - - } - - /** - * This method returns a node for a principal. - * - * The passed array contains principal information, and is guaranteed to - * at least contain a uri item. Other properties may or may not be - * supplied by the authentication backend. - * - * @param array $principal - * @return \Sabre\DAV\INode - */ - public function getChildForPrincipal(array $principal) { - - return new UserAddressBooks($this->carddavBackend, $principal['uri']); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CardDAV/Backend/AbstractBackend.php b/vendor/sabre/dav/lib/Sabre/CardDAV/Backend/AbstractBackend.php deleted file mode 100644 index 46909efef..000000000 --- a/vendor/sabre/dav/lib/Sabre/CardDAV/Backend/AbstractBackend.php +++ /dev/null @@ -1,18 +0,0 @@ -pdo = $pdo; - $this->addressBooksTableName = $addressBooksTableName; - $this->cardsTableName = $cardsTableName; - - } - - /** - * Returns the list of addressbooks for a specific user. - * - * @param string $principalUri - * @return array - */ - public function getAddressBooksForUser($principalUri) { - - $stmt = $this->pdo->prepare('SELECT id, uri, displayname, principaluri, description, ctag FROM '.$this->addressBooksTableName.' WHERE principaluri = ?'); - $stmt->execute(array($principalUri)); - - $addressBooks = array(); - - foreach($stmt->fetchAll() as $row) { - - $addressBooks[] = array( - 'id' => $row['id'], - 'uri' => $row['uri'], - 'principaluri' => $row['principaluri'], - '{DAV:}displayname' => $row['displayname'], - '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'], - '{http://calendarserver.org/ns/}getctag' => $row['ctag'], - '{' . CardDAV\Plugin::NS_CARDDAV . '}supported-address-data' => - new CardDAV\Property\SupportedAddressData(), - ); - - } - - return $addressBooks; - - } - - - /** - * Updates an addressbook's properties - * - * See Sabre\DAV\IProperties for a description of the mutations array, as - * well as the return value. - * - * @param mixed $addressBookId - * @param array $mutations - * @see Sabre\DAV\IProperties::updateProperties - * @return bool|array - */ - public function updateAddressBook($addressBookId, array $mutations) { - - $updates = array(); - - foreach($mutations as $property=>$newValue) { - - switch($property) { - case '{DAV:}displayname' : - $updates['displayname'] = $newValue; - break; - case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' : - $updates['description'] = $newValue; - break; - default : - // If any unsupported values were being updated, we must - // let the entire request fail. - return false; - } - - } - - // No values are being updated? - if (!$updates) { - return false; - } - - $query = 'UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 '; - foreach($updates as $key=>$value) { - $query.=', `' . $key . '` = :' . $key . ' '; - } - $query.=' WHERE id = :addressbookid'; - - $stmt = $this->pdo->prepare($query); - $updates['addressbookid'] = $addressBookId; - - $stmt->execute($updates); - - return true; - - } - - /** - * Creates a new address book - * - * @param string $principalUri - * @param string $url Just the 'basename' of the url. - * @param array $properties - * @return void - */ - public function createAddressBook($principalUri, $url, array $properties) { - - $values = array( - 'displayname' => null, - 'description' => null, - 'principaluri' => $principalUri, - 'uri' => $url, - ); - - foreach($properties as $property=>$newValue) { - - switch($property) { - case '{DAV:}displayname' : - $values['displayname'] = $newValue; - break; - case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' : - $values['description'] = $newValue; - break; - default : - throw new DAV\Exception\BadRequest('Unknown property: ' . $property); - } - - } - - $query = 'INSERT INTO ' . $this->addressBooksTableName . ' (uri, displayname, description, principaluri, ctag) VALUES (:uri, :displayname, :description, :principaluri, 1)'; - $stmt = $this->pdo->prepare($query); - $stmt->execute($values); - - } - - /** - * Deletes an entire addressbook and all its contents - * - * @param int $addressBookId - * @return void - */ - public function deleteAddressBook($addressBookId) { - - $stmt = $this->pdo->prepare('DELETE FROM ' . $this->cardsTableName . ' WHERE addressbookid = ?'); - $stmt->execute(array($addressBookId)); - - $stmt = $this->pdo->prepare('DELETE FROM ' . $this->addressBooksTableName . ' WHERE id = ?'); - $stmt->execute(array($addressBookId)); - - } - - /** - * Returns all cards for a specific addressbook id. - * - * This method should return the following properties for each card: - * * carddata - raw vcard data - * * uri - Some unique url - * * lastmodified - A unix timestamp - * - * It's recommended to also return the following properties: - * * etag - A unique etag. This must change every time the card changes. - * * size - The size of the card in bytes. - * - * If these last two properties are provided, less time will be spent - * calculating them. If they are specified, you can also ommit carddata. - * This may speed up certain requests, especially with large cards. - * - * @param mixed $addressbookId - * @return array - */ - public function getCards($addressbookId) { - - $stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified FROM ' . $this->cardsTableName . ' WHERE addressbookid = ?'); - $stmt->execute(array($addressbookId)); - - return $stmt->fetchAll(\PDO::FETCH_ASSOC); - - - } - - /** - * Returns a specfic card. - * - * The same set of properties must be returned as with getCards. The only - * exception is that 'carddata' is absolutely required. - * - * @param mixed $addressBookId - * @param string $cardUri - * @return array - */ - public function getCard($addressBookId, $cardUri) { - - $stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri = ? LIMIT 1'); - $stmt->execute(array($addressBookId, $cardUri)); - - $result = $stmt->fetchAll(\PDO::FETCH_ASSOC); - - return (count($result)>0?$result[0]:false); - - } - - /** - * Creates a new card. - * - * The addressbook id will be passed as the first argument. This is the - * same id as it is returned from the getAddressbooksForUser method. - * - * The cardUri is a base uri, and doesn't include the full path. The - * cardData argument is the vcard body, and is passed as a string. - * - * It is possible to return an ETag from this method. This ETag is for the - * newly created resource, and must be enclosed with double quotes (that - * is, the string itself must contain the double quotes). - * - * You should only return the ETag if you store the carddata as-is. If a - * subsequent GET request on the same card does not have the same body, - * byte-by-byte and you did return an ETag here, clients tend to get - * confused. - * - * If you don't return an ETag, you can just return null. - * - * @param mixed $addressBookId - * @param string $cardUri - * @param string $cardData - * @return string|null - */ - public function createCard($addressBookId, $cardUri, $cardData) { - - $stmt = $this->pdo->prepare('INSERT INTO ' . $this->cardsTableName . ' (carddata, uri, lastmodified, addressbookid) VALUES (?, ?, ?, ?)'); - - $result = $stmt->execute(array($cardData, $cardUri, time(), $addressBookId)); - - $stmt2 = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 WHERE id = ?'); - $stmt2->execute(array($addressBookId)); - - return '"' . md5($cardData) . '"'; - - } - - /** - * Updates a card. - * - * The addressbook id will be passed as the first argument. This is the - * same id as it is returned from the getAddressbooksForUser method. - * - * The cardUri is a base uri, and doesn't include the full path. The - * cardData argument is the vcard body, and is passed as a string. - * - * It is possible to return an ETag from this method. This ETag should - * match that of the updated resource, and must be enclosed with double - * quotes (that is: the string itself must contain the actual quotes). - * - * You should only return the ETag if you store the carddata as-is. If a - * subsequent GET request on the same card does not have the same body, - * byte-by-byte and you did return an ETag here, clients tend to get - * confused. - * - * If you don't return an ETag, you can just return null. - * - * @param mixed $addressBookId - * @param string $cardUri - * @param string $cardData - * @return string|null - */ - public function updateCard($addressBookId, $cardUri, $cardData) { - - $stmt = $this->pdo->prepare('UPDATE ' . $this->cardsTableName . ' SET carddata = ?, lastmodified = ? WHERE uri = ? AND addressbookid =?'); - $stmt->execute(array($cardData, time(), $cardUri, $addressBookId)); - - $stmt2 = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 WHERE id = ?'); - $stmt2->execute(array($addressBookId)); - - return '"' . md5($cardData) . '"'; - - } - - /** - * Deletes a card - * - * @param mixed $addressBookId - * @param string $cardUri - * @return bool - */ - public function deleteCard($addressBookId, $cardUri) { - - $stmt = $this->pdo->prepare('DELETE FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri = ?'); - $stmt->execute(array($addressBookId, $cardUri)); - - $stmt2 = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET ctag = ctag + 1 WHERE id = ?'); - $stmt2->execute(array($addressBookId)); - - return $stmt->rowCount()===1; - - } -} diff --git a/vendor/sabre/dav/lib/Sabre/CardDAV/Card.php b/vendor/sabre/dav/lib/Sabre/CardDAV/Card.php deleted file mode 100644 index cc65f7600..000000000 --- a/vendor/sabre/dav/lib/Sabre/CardDAV/Card.php +++ /dev/null @@ -1,260 +0,0 @@ -carddavBackend = $carddavBackend; - $this->addressBookInfo = $addressBookInfo; - $this->cardData = $cardData; - - } - - /** - * Returns the uri for this object - * - * @return string - */ - public function getName() { - - return $this->cardData['uri']; - - } - - /** - * Returns the VCard-formatted object - * - * @return string - */ - public function get() { - - // Pre-populating 'carddata' is optional. If we don't yet have it - // already, we fetch it from the backend. - if (!isset($this->cardData['carddata'])) { - $this->cardData = $this->carddavBackend->getCard($this->addressBookInfo['id'], $this->cardData['uri']); - } - return $this->cardData['carddata']; - - } - - /** - * Updates the VCard-formatted object - * - * @param string $cardData - * @return string|null - */ - public function put($cardData) { - - if (is_resource($cardData)) - $cardData = stream_get_contents($cardData); - - // Converting to UTF-8, if needed - $cardData = DAV\StringUtil::ensureUTF8($cardData); - - $etag = $this->carddavBackend->updateCard($this->addressBookInfo['id'],$this->cardData['uri'],$cardData); - $this->cardData['carddata'] = $cardData; - $this->cardData['etag'] = $etag; - - return $etag; - - } - - /** - * Deletes the card - * - * @return void - */ - public function delete() { - - $this->carddavBackend->deleteCard($this->addressBookInfo['id'],$this->cardData['uri']); - - } - - /** - * Returns the mime content-type - * - * @return string - */ - public function getContentType() { - - return 'text/x-vcard; charset=utf-8'; - - } - - /** - * Returns an ETag for this object - * - * @return string - */ - public function getETag() { - - if (isset($this->cardData['etag'])) { - return $this->cardData['etag']; - } else { - $data = $this->get(); - if (is_string($data)) { - return '"' . md5($data) . '"'; - } else { - // We refuse to calculate the md5 if it's a stream. - return null; - } - } - - } - - /** - * Returns the last modification date as a unix timestamp - * - * @return int - */ - public function getLastModified() { - - return isset($this->cardData['lastmodified'])?$this->cardData['lastmodified']:null; - - } - - /** - * Returns the size of this object in bytes - * - * @return int - */ - public function getSize() { - - if (array_key_exists('size', $this->cardData)) { - return $this->cardData['size']; - } else { - return strlen($this->get()); - } - - } - - /** - * Returns the owner principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - public function getOwner() { - - return $this->addressBookInfo['principaluri']; - - } - - /** - * Returns a group principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - public function getGroup() { - - return null; - - } - - /** - * Returns a list of ACE's for this node. - * - * Each ACE has the following properties: - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are - * currently the only supported privileges - * * 'principal', a url to the principal who owns the node - * * 'protected' (optional), indicating that this ACE is not allowed to - * be updated. - * - * @return array - */ - public function getACL() { - - return array( - array( - 'privilege' => '{DAV:}read', - 'principal' => $this->addressBookInfo['principaluri'], - 'protected' => true, - ), - array( - 'privilege' => '{DAV:}write', - 'principal' => $this->addressBookInfo['principaluri'], - 'protected' => true, - ), - ); - - } - - /** - * Updates the ACL - * - * This method will receive a list of new ACE's. - * - * @param array $acl - * @return void - */ - public function setACL(array $acl) { - - throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported'); - - } - - /** - * Returns the list of supported privileges for this node. - * - * The returned data structure is a list of nested privileges. - * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple - * standard structure. - * - * If null is returned from this method, the default privilege set is used, - * which is fine for most common usecases. - * - * @return array|null - */ - public function getSupportedPrivilegeSet() { - - return null; - - } - -} - diff --git a/vendor/sabre/dav/lib/Sabre/CardDAV/IAddressBook.php b/vendor/sabre/dav/lib/Sabre/CardDAV/IAddressBook.php deleted file mode 100644 index e9e990cbd..000000000 --- a/vendor/sabre/dav/lib/Sabre/CardDAV/IAddressBook.php +++ /dev/null @@ -1,20 +0,0 @@ -subscribeEvent('beforeGetProperties', array($this, 'beforeGetProperties')); - $server->subscribeEvent('afterGetProperties', array($this, 'afterGetProperties')); - $server->subscribeEvent('updateProperties', array($this, 'updateProperties')); - $server->subscribeEvent('report', array($this,'report')); - $server->subscribeEvent('onHTMLActionsPanel', array($this,'htmlActionsPanel')); - $server->subscribeEvent('onBrowserPostAction', array($this,'browserPostAction')); - $server->subscribeEvent('beforeWriteContent', array($this, 'beforeWriteContent')); - $server->subscribeEvent('beforeCreateFile', array($this, 'beforeCreateFile')); - - /* Namespaces */ - $server->xmlNamespaces[self::NS_CARDDAV] = 'card'; - - /* Mapping Interfaces to {DAV:}resourcetype values */ - $server->resourceTypeMapping['Sabre\\CardDAV\\IAddressBook'] = '{' . self::NS_CARDDAV . '}addressbook'; - $server->resourceTypeMapping['Sabre\\CardDAV\\IDirectory'] = '{' . self::NS_CARDDAV . '}directory'; - - /* Adding properties that may never be changed */ - $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}supported-address-data'; - $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}max-resource-size'; - $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}addressbook-home-set'; - $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}supported-collation-set'; - - $server->propertyMap['{http://calendarserver.org/ns/}me-card'] = 'Sabre\\DAV\\Property\\Href'; - - $this->server = $server; - - } - - /** - * Returns a list of supported features. - * - * This is used in the DAV: header in the OPTIONS and PROPFIND requests. - * - * @return array - */ - public function getFeatures() { - - return array('addressbook'); - - } - - /** - * Returns a list of reports this plugin supports. - * - * This will be used in the {DAV:}supported-report-set property. - * Note that you still need to subscribe to the 'report' event to actually - * implement them - * - * @param string $uri - * @return array - */ - public function getSupportedReportSet($uri) { - - $node = $this->server->tree->getNodeForPath($uri); - if ($node instanceof IAddressBook || $node instanceof ICard) { - return array( - '{' . self::NS_CARDDAV . '}addressbook-multiget', - '{' . self::NS_CARDDAV . '}addressbook-query', - ); - } - return array(); - - } - - - /** - * Adds all CardDAV-specific properties - * - * @param string $path - * @param DAV\INode $node - * @param array $requestedProperties - * @param array $returnedProperties - * @return void - */ - public function beforeGetProperties($path, DAV\INode $node, array &$requestedProperties, array &$returnedProperties) { - - if ($node instanceof DAVACL\IPrincipal) { - - // calendar-home-set property - $addHome = '{' . self::NS_CARDDAV . '}addressbook-home-set'; - if (in_array($addHome,$requestedProperties)) { - $principalId = $node->getName(); - $addressbookHomePath = self::ADDRESSBOOK_ROOT . '/' . $principalId . '/'; - unset($requestedProperties[array_search($addHome, $requestedProperties)]); - $returnedProperties[200][$addHome] = new DAV\Property\Href($addressbookHomePath); - } - - $directories = '{' . self::NS_CARDDAV . '}directory-gateway'; - if ($this->directories && in_array($directories, $requestedProperties)) { - unset($requestedProperties[array_search($directories, $requestedProperties)]); - $returnedProperties[200][$directories] = new DAV\Property\HrefList($this->directories); - } - - } - - if ($node instanceof ICard) { - - // The address-data property is not supposed to be a 'real' - // property, but in large chunks of the spec it does act as such. - // Therefore we simply expose it as a property. - $addressDataProp = '{' . self::NS_CARDDAV . '}address-data'; - if (in_array($addressDataProp, $requestedProperties)) { - unset($requestedProperties[$addressDataProp]); - $val = $node->get(); - if (is_resource($val)) - $val = stream_get_contents($val); - - $returnedProperties[200][$addressDataProp] = $val; - - } - } - - if ($node instanceof UserAddressBooks) { - - $meCardProp = '{http://calendarserver.org/ns/}me-card'; - if (in_array($meCardProp, $requestedProperties)) { - - $props = $this->server->getProperties($node->getOwner(), array('{http://sabredav.org/ns}vcard-url')); - if (isset($props['{http://sabredav.org/ns}vcard-url'])) { - - $returnedProperties[200][$meCardProp] = new DAV\Property\Href( - $props['{http://sabredav.org/ns}vcard-url'] - ); - $pos = array_search($meCardProp, $requestedProperties); - unset($requestedProperties[$pos]); - - } - - } - - } - - } - - /** - * This event is triggered when a PROPPATCH method is executed - * - * @param array $mutations - * @param array $result - * @param DAV\INode $node - * @return bool - */ - public function updateProperties(&$mutations, &$result, DAV\INode $node) { - - if (!$node instanceof UserAddressBooks) { - return true; - } - - $meCard = '{http://calendarserver.org/ns/}me-card'; - - // The only property we care about - if (!isset($mutations[$meCard])) - return true; - - $value = $mutations[$meCard]; - unset($mutations[$meCard]); - - if ($value instanceof DAV\Property\IHref) { - $value = $value->getHref(); - $value = $this->server->calculateUri($value); - } elseif (!is_null($value)) { - $result[400][$meCard] = null; - return false; - } - - $innerResult = $this->server->updateProperties( - $node->getOwner(), - array( - '{http://sabredav.org/ns}vcard-url' => $value, - ) - ); - - $closureResult = false; - foreach($innerResult as $status => $props) { - if (is_array($props) && array_key_exists('{http://sabredav.org/ns}vcard-url', $props)) { - $result[$status][$meCard] = null; - $closureResult = ($status>=200 && $status<300); - } - - } - - return $result; - - } - - /** - * This functions handles REPORT requests specific to CardDAV - * - * @param string $reportName - * @param \DOMNode $dom - * @return bool - */ - public function report($reportName,$dom) { - - switch($reportName) { - case '{'.self::NS_CARDDAV.'}addressbook-multiget' : - $this->addressbookMultiGetReport($dom); - return false; - case '{'.self::NS_CARDDAV.'}addressbook-query' : - $this->addressBookQueryReport($dom); - return false; - default : - return; - - } - - - } - - /** - * This function handles the addressbook-multiget REPORT. - * - * This report is used by the client to fetch the content of a series - * of urls. Effectively avoiding a lot of redundant requests. - * - * @param \DOMNode $dom - * @return void - */ - public function addressbookMultiGetReport($dom) { - - $properties = array_keys(DAV\XMLUtil::parseProperties($dom->firstChild)); - - $hrefElems = $dom->getElementsByTagNameNS('urn:DAV','href'); - $propertyList = array(); - - foreach($hrefElems as $elem) { - - $uri = $this->server->calculateUri($elem->nodeValue); - list($propertyList[]) = $this->server->getPropertiesForPath($uri,$properties); - - } - - $prefer = $this->server->getHTTPPRefer(); - - $this->server->httpResponse->sendStatus(207); - $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); - $this->server->httpResponse->setHeader('Vary','Brief,Prefer'); - $this->server->httpResponse->sendBody($this->server->generateMultiStatus($propertyList, $prefer['return-minimal'])); - - } - - /** - * This method is triggered before a file gets updated with new content. - * - * This plugin uses this method to ensure that Card nodes receive valid - * vcard data. - * - * @param string $path - * @param DAV\IFile $node - * @param resource $data - * @return void - */ - public function beforeWriteContent($path, DAV\IFile $node, &$data) { - - if (!$node instanceof ICard) - return; - - $this->validateVCard($data); - - } - - /** - * This method is triggered before a new file is created. - * - * This plugin uses this method to ensure that Card nodes receive valid - * vcard data. - * - * @param string $path - * @param resource $data - * @param DAV\ICollection $parentNode - * @return void - */ - public function beforeCreateFile($path, &$data, DAV\ICollection $parentNode) { - - if (!$parentNode instanceof IAddressBook) - return; - - $this->validateVCard($data); - - } - - /** - * Checks if the submitted iCalendar data is in fact, valid. - * - * An exception is thrown if it's not. - * - * @param resource|string $data - * @return void - */ - protected function validateVCard(&$data) { - - // If it's a stream, we convert it to a string first. - if (is_resource($data)) { - $data = stream_get_contents($data); - } - - // Converting the data to unicode, if needed. - $data = DAV\StringUtil::ensureUTF8($data); - - try { - - $vobj = VObject\Reader::read($data); - - } catch (VObject\ParseException $e) { - - throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid vcard data. Parse error: ' . $e->getMessage()); - - } - - if ($vobj->name !== 'VCARD') { - throw new DAV\Exception\UnsupportedMediaType('This collection can only support vcard objects.'); - } - - if (!isset($vobj->UID)) { - // No UID in vcards is invalid, but we'll just add it in anyway. - $vobj->add('UID', DAV\UUIDUtil::getUUID()); - $data = $vobj->serialize(); - } - - } - - - /** - * This function handles the addressbook-query REPORT - * - * This report is used by the client to filter an addressbook based on a - * complex query. - * - * @param \DOMNode $dom - * @return void - */ - protected function addressbookQueryReport($dom) { - - $query = new AddressBookQueryParser($dom); - $query->parse(); - - $depth = $this->server->getHTTPDepth(0); - - if ($depth==0) { - $candidateNodes = array( - $this->server->tree->getNodeForPath($this->server->getRequestUri()) - ); - } else { - $candidateNodes = $this->server->tree->getChildren($this->server->getRequestUri()); - } - - $validNodes = array(); - foreach($candidateNodes as $node) { - - if (!$node instanceof ICard) - continue; - - $blob = $node->get(); - if (is_resource($blob)) { - $blob = stream_get_contents($blob); - } - - if (!$this->validateFilters($blob, $query->filters, $query->test)) { - continue; - } - - $validNodes[] = $node; - - if ($query->limit && $query->limit <= count($validNodes)) { - // We hit the maximum number of items, we can stop now. - break; - } - - } - - $result = array(); - foreach($validNodes as $validNode) { - - if ($depth==0) { - $href = $this->server->getRequestUri(); - } else { - $href = $this->server->getRequestUri() . '/' . $validNode->getName(); - } - - list($result[]) = $this->server->getPropertiesForPath($href, $query->requestedProperties, 0); - - } - - $prefer = $this->server->getHTTPPRefer(); - - $this->server->httpResponse->sendStatus(207); - $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); - $this->server->httpResponse->setHeader('Vary','Brief,Prefer'); - $this->server->httpResponse->sendBody($this->server->generateMultiStatus($result, $prefer['return-minimal'])); - - } - - /** - * Validates if a vcard makes it throught a list of filters. - * - * @param string $vcardData - * @param array $filters - * @param string $test anyof or allof (which means OR or AND) - * @return bool - */ - public function validateFilters($vcardData, array $filters, $test) { - - $vcard = VObject\Reader::read($vcardData); - - if (!$filters) return true; - - foreach($filters as $filter) { - - $isDefined = isset($vcard->{$filter['name']}); - if ($filter['is-not-defined']) { - if ($isDefined) { - $success = false; - } else { - $success = true; - } - } elseif ((!$filter['param-filters'] && !$filter['text-matches']) || !$isDefined) { - - // We only need to check for existence - $success = $isDefined; - - } else { - - $vProperties = $vcard->select($filter['name']); - - $results = array(); - if ($filter['param-filters']) { - $results[] = $this->validateParamFilters($vProperties, $filter['param-filters'], $filter['test']); - } - if ($filter['text-matches']) { - $texts = array(); - foreach($vProperties as $vProperty) - $texts[] = $vProperty->getValue(); - - $results[] = $this->validateTextMatches($texts, $filter['text-matches'], $filter['test']); - } - - if (count($results)===1) { - $success = $results[0]; - } else { - if ($filter['test'] === 'anyof') { - $success = $results[0] || $results[1]; - } else { - $success = $results[0] && $results[1]; - } - } - - } // else - - // There are two conditions where we can already determine whether - // or not this filter succeeds. - if ($test==='anyof' && $success) { - return true; - } - if ($test==='allof' && !$success) { - return false; - } - - } // foreach - - // If we got all the way here, it means we haven't been able to - // determine early if the test failed or not. - // - // This implies for 'anyof' that the test failed, and for 'allof' that - // we succeeded. Sounds weird, but makes sense. - return $test==='allof'; - - } - - /** - * Validates if a param-filter can be applied to a specific property. - * - * @todo currently we're only validating the first parameter of the passed - * property. Any subsequence parameters with the same name are - * ignored. - * @param array $vProperties - * @param array $filters - * @param string $test - * @return bool - */ - protected function validateParamFilters(array $vProperties, array $filters, $test) { - - foreach($filters as $filter) { - - $isDefined = false; - foreach($vProperties as $vProperty) { - $isDefined = isset($vProperty[$filter['name']]); - if ($isDefined) break; - } - - if ($filter['is-not-defined']) { - if ($isDefined) { - $success = false; - } else { - $success = true; - } - - // If there's no text-match, we can just check for existence - } elseif (!$filter['text-match'] || !$isDefined) { - - $success = $isDefined; - - } else { - - $success = false; - foreach($vProperties as $vProperty) { - // If we got all the way here, we'll need to validate the - // text-match filter. - $success = DAV\StringUtil::textMatch($vProperty[$filter['name']]->getValue(), $filter['text-match']['value'], $filter['text-match']['collation'], $filter['text-match']['match-type']); - if ($success) break; - } - if ($filter['text-match']['negate-condition']) { - $success = !$success; - } - - } // else - - // There are two conditions where we can already determine whether - // or not this filter succeeds. - if ($test==='anyof' && $success) { - return true; - } - if ($test==='allof' && !$success) { - return false; - } - - } - - // If we got all the way here, it means we haven't been able to - // determine early if the test failed or not. - // - // This implies for 'anyof' that the test failed, and for 'allof' that - // we succeeded. Sounds weird, but makes sense. - return $test==='allof'; - - } - - /** - * Validates if a text-filter can be applied to a specific property. - * - * @param array $texts - * @param array $filters - * @param string $test - * @return bool - */ - protected function validateTextMatches(array $texts, array $filters, $test) { - - foreach($filters as $filter) { - - $success = false; - foreach($texts as $haystack) { - $success = DAV\StringUtil::textMatch($haystack, $filter['value'], $filter['collation'], $filter['match-type']); - - // Breaking on the first match - if ($success) break; - } - if ($filter['negate-condition']) { - $success = !$success; - } - - if ($success && $test==='anyof') - return true; - - if (!$success && $test=='allof') - return false; - - - } - - // If we got all the way here, it means we haven't been able to - // determine early if the test failed or not. - // - // This implies for 'anyof' that the test failed, and for 'allof' that - // we succeeded. Sounds weird, but makes sense. - return $test==='allof'; - - } - - /** - * This event is triggered after webdav-properties have been retrieved. - * - * @return bool - */ - public function afterGetProperties($uri, &$properties) { - - // If the request was made using the SOGO connector, we must rewrite - // the content-type property. By default SabreDAV will send back - // text/x-vcard; charset=utf-8, but for SOGO we must strip that last - // part. - if (!isset($properties[200]['{DAV:}getcontenttype'])) - return; - - if (strpos($this->server->httpRequest->getHeader('User-Agent'),'Thunderbird')===false) { - return; - } - - if (strpos($properties[200]['{DAV:}getcontenttype'],'text/x-vcard')===0) { - $properties[200]['{DAV:}getcontenttype'] = 'text/x-vcard'; - } - - } - - /** - * This method is used to generate HTML output for the - * Sabre\DAV\Browser\Plugin. This allows us to generate an interface users - * can use to create new calendars. - * - * @param DAV\INode $node - * @param string $output - * @return bool - */ - public function htmlActionsPanel(DAV\INode $node, &$output) { - - if (!$node instanceof UserAddressBooks) - return; - - $output.= '
    -

    Create new address book

    - -
    -
    - -
    - '; - - return false; - - } - - /** - * This method allows us to intercept the 'mkcalendar' sabreAction. This - * action enables the user to create new calendars from the browser plugin. - * - * @param string $uri - * @param string $action - * @param array $postVars - * @return bool - */ - public function browserPostAction($uri, $action, array $postVars) { - - if ($action!=='mkaddressbook') - return; - - $resourceType = array('{DAV:}collection','{urn:ietf:params:xml:ns:carddav}addressbook'); - $properties = array(); - if (isset($postVars['{DAV:}displayname'])) { - $properties['{DAV:}displayname'] = $postVars['{DAV:}displayname']; - } - $this->server->createCollection($uri . '/' . $postVars['name'],$resourceType,$properties); - return false; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CardDAV/Property/SupportedAddressData.php b/vendor/sabre/dav/lib/Sabre/CardDAV/Property/SupportedAddressData.php deleted file mode 100644 index 9d8dd2e6d..000000000 --- a/vendor/sabre/dav/lib/Sabre/CardDAV/Property/SupportedAddressData.php +++ /dev/null @@ -1,72 +0,0 @@ - 'text/vcard', 'version' => '3.0'), - // array('contentType' => 'text/vcard', 'version' => '4.0'), - ); - } - - $this->supportedData = $supportedData; - - } - - /** - * Serializes the property in a DOMDocument - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - public function serialize(DAV\Server $server,\DOMElement $node) { - - $doc = $node->ownerDocument; - - $prefix = - isset($server->xmlNamespaces[CardDAV\Plugin::NS_CARDDAV]) ? - $server->xmlNamespaces[CardDAV\Plugin::NS_CARDDAV] : - 'card'; - - foreach($this->supportedData as $supported) { - - $caldata = $doc->createElementNS(CardDAV\Plugin::NS_CARDDAV, $prefix . ':address-data-type'); - $caldata->setAttribute('content-type',$supported['contentType']); - $caldata->setAttribute('version',$supported['version']); - $node->appendChild($caldata); - - } - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CardDAV/UserAddressBooks.php b/vendor/sabre/dav/lib/Sabre/CardDAV/UserAddressBooks.php deleted file mode 100644 index b4af86147..000000000 --- a/vendor/sabre/dav/lib/Sabre/CardDAV/UserAddressBooks.php +++ /dev/null @@ -1,260 +0,0 @@ -carddavBackend = $carddavBackend; - $this->principalUri = $principalUri; - - } - - /** - * Returns the name of this object - * - * @return string - */ - public function getName() { - - list(,$name) = DAV\URLUtil::splitPath($this->principalUri); - return $name; - - } - - /** - * Updates the name of this object - * - * @param string $name - * @return void - */ - public function setName($name) { - - throw new DAV\Exception\MethodNotAllowed(); - - } - - /** - * Deletes this object - * - * @return void - */ - public function delete() { - - throw new DAV\Exception\MethodNotAllowed(); - - } - - /** - * Returns the last modification date - * - * @return int - */ - public function getLastModified() { - - return null; - - } - - /** - * Creates a new file under this object. - * - * This is currently not allowed - * - * @param string $filename - * @param resource $data - * @return void - */ - public function createFile($filename, $data=null) { - - throw new DAV\Exception\MethodNotAllowed('Creating new files in this collection is not supported'); - - } - - /** - * Creates a new directory under this object. - * - * This is currently not allowed. - * - * @param string $filename - * @return void - */ - public function createDirectory($filename) { - - throw new DAV\Exception\MethodNotAllowed('Creating new collections in this collection is not supported'); - - } - - /** - * Returns a single calendar, by name - * - * @param string $name - * @todo needs optimizing - * @return \AddressBook - */ - public function getChild($name) { - - foreach($this->getChildren() as $child) { - if ($name==$child->getName()) - return $child; - - } - throw new DAV\Exception\NotFound('Addressbook with name \'' . $name . '\' could not be found'); - - } - - /** - * Returns a list of addressbooks - * - * @return array - */ - public function getChildren() { - - $addressbooks = $this->carddavBackend->getAddressbooksForUser($this->principalUri); - $objs = array(); - foreach($addressbooks as $addressbook) { - $objs[] = new AddressBook($this->carddavBackend, $addressbook); - } - return $objs; - - } - - /** - * Creates a new addressbook - * - * @param string $name - * @param array $resourceType - * @param array $properties - * @return void - */ - public function createExtendedCollection($name, array $resourceType, array $properties) { - - if (!in_array('{'.Plugin::NS_CARDDAV.'}addressbook',$resourceType) || count($resourceType)!==2) { - throw new DAV\Exception\InvalidResourceType('Unknown resourceType for this collection'); - } - $this->carddavBackend->createAddressBook($this->principalUri, $name, $properties); - - } - - /** - * Returns the owner principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - public function getOwner() { - - return $this->principalUri; - - } - - /** - * Returns a group principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - public function getGroup() { - - return null; - - } - - /** - * Returns a list of ACE's for this node. - * - * Each ACE has the following properties: - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are - * currently the only supported privileges - * * 'principal', a url to the principal who owns the node - * * 'protected' (optional), indicating that this ACE is not allowed to - * be updated. - * - * @return array - */ - public function getACL() { - - return array( - array( - 'privilege' => '{DAV:}read', - 'principal' => $this->principalUri, - 'protected' => true, - ), - array( - 'privilege' => '{DAV:}write', - 'principal' => $this->principalUri, - 'protected' => true, - ), - - ); - - } - - /** - * Updates the ACL - * - * This method will receive a list of new ACE's. - * - * @param array $acl - * @return void - */ - public function setACL(array $acl) { - - throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported'); - - } - - /** - * Returns the list of supported privileges for this node. - * - * The returned data structure is a list of nested privileges. - * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple - * standard structure. - * - * If null is returned from this method, the default privilege set is used, - * which is fine for most common usecases. - * - * @return array|null - */ - public function getSupportedPrivilegeSet() { - - return null; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CardDAV/VCFExportPlugin.php b/vendor/sabre/dav/lib/Sabre/CardDAV/VCFExportPlugin.php deleted file mode 100644 index 3f91a3012..000000000 --- a/vendor/sabre/dav/lib/Sabre/CardDAV/VCFExportPlugin.php +++ /dev/null @@ -1,108 +0,0 @@ -server = $server; - $this->server->subscribeEvent('beforeMethod',array($this,'beforeMethod'), 90); - - } - - /** - * 'beforeMethod' event handles. This event handles intercepts GET requests ending - * with ?export - * - * @param string $method - * @param string $uri - * @return bool - */ - public function beforeMethod($method, $uri) { - - if ($method!='GET') return; - if ($this->server->httpRequest->getQueryString()!='export') return; - - // splitting uri - list($uri) = explode('?',$uri,2); - - $node = $this->server->tree->getNodeForPath($uri); - - if (!($node instanceof IAddressBook)) return; - - // Checking ACL, if available. - if ($aclPlugin = $this->server->getPlugin('acl')) { - $aclPlugin->checkPrivileges($uri, '{DAV:}read'); - } - - $this->server->httpResponse->setHeader('Content-Type','text/directory'); - $this->server->httpResponse->sendStatus(200); - - $nodes = $this->server->getPropertiesForPath($uri, array( - '{' . Plugin::NS_CARDDAV . '}address-data', - ),1); - - $this->server->httpResponse->sendBody($this->generateVCF($nodes)); - - // Returning false to break the event chain - return false; - - } - - /** - * Merges all vcard objects, and builds one big vcf export - * - * @param array $nodes - * @return string - */ - public function generateVCF(array $nodes) { - - $output = ""; - - foreach($nodes as $node) { - - if (!isset($node[200]['{' . Plugin::NS_CARDDAV . '}address-data'])) { - continue; - } - $nodeData = $node[200]['{' . Plugin::NS_CARDDAV . '}address-data']; - - // Parsing this node so VObject can clean up the output. - $output .= - VObject\Reader::read($nodeData)->serialize(); - - } - - return $output; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/CardDAV/Version.php b/vendor/sabre/dav/lib/Sabre/CardDAV/Version.php deleted file mode 100644 index 00221941b..000000000 --- a/vendor/sabre/dav/lib/Sabre/CardDAV/Version.php +++ /dev/null @@ -1,26 +0,0 @@ -currentUser; - } - - - /** - * Authenticates the user based on the current request. - * - * If authentication is successful, true must be returned. - * If authentication fails, an exception must be thrown. - * - * @param DAV\Server $server - * @param string $realm - * @throws DAV\Exception\NotAuthenticated - * @return bool - */ - public function authenticate(DAV\Server $server, $realm) { - - $auth = new HTTP\BasicAuth(); - $auth->setHTTPRequest($server->httpRequest); - $auth->setHTTPResponse($server->httpResponse); - $auth->setRealm($realm); - $userpass = $auth->getUserPass(); - if (!$userpass) { - $auth->requireLogin(); - throw new DAV\Exception\NotAuthenticated('No basic authentication headers were found'); - } - - // Authenticates the user - if (!$this->validateUserPass($userpass[0],$userpass[1])) { - $auth->requireLogin(); - throw new DAV\Exception\NotAuthenticated('Username or password does not match'); - } - $this->currentUser = $userpass[0]; - return true; - } - - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/AbstractDigest.php b/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/AbstractDigest.php deleted file mode 100644 index dc00438c9..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/AbstractDigest.php +++ /dev/null @@ -1,101 +0,0 @@ -setHTTPRequest($server->httpRequest); - $digest->setHTTPResponse($server->httpResponse); - - $digest->setRealm($realm); - $digest->init(); - - $username = $digest->getUsername(); - - // No username was given - if (!$username) { - $digest->requireLogin(); - throw new DAV\Exception\NotAuthenticated('No digest authentication headers were found'); - } - - $hash = $this->getDigestHash($realm, $username); - // If this was false, the user account didn't exist - if ($hash===false || is_null($hash)) { - $digest->requireLogin(); - throw new DAV\Exception\NotAuthenticated('The supplied username was not on file'); - } - if (!is_string($hash)) { - throw new DAV\Exception('The returned value from getDigestHash must be a string or null'); - } - - // If this was false, the password or part of the hash was incorrect. - if (!$digest->validateA1($hash)) { - $digest->requireLogin(); - throw new DAV\Exception\NotAuthenticated('Incorrect username'); - } - - $this->currentUser = $username; - return true; - - } - - /** - * Returns the currently logged in username. - * - * @return string|null - */ - public function getCurrentUser() { - - return $this->currentUser; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/Apache.php b/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/Apache.php deleted file mode 100644 index 66fdd91e1..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/Apache.php +++ /dev/null @@ -1,63 +0,0 @@ -httpRequest->getRawServerValue('REMOTE_USER'); - if (is_null($remoteUser)) { - throw new DAV\Exception('We did not receive the $_SERVER[REMOTE_USER] property. This means that apache might have been misconfigured'); - } - - $this->remoteUser = $remoteUser; - return true; - - } - - /** - * Returns information about the currently logged in user. - * - * If nobody is currently logged in, this method should return null. - * - * @return array|null - */ - public function getCurrentUser() { - - return $this->remoteUser; - - } - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/BackendInterface.php b/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/BackendInterface.php deleted file mode 100644 index b8d04e2e1..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/BackendInterface.php +++ /dev/null @@ -1,36 +0,0 @@ -loadFile($filename); - - } - - /** - * Loads an htdigest-formatted file. This method can be called multiple times if - * more than 1 file is used. - * - * @param string $filename - * @return void - */ - public function loadFile($filename) { - - foreach(file($filename,FILE_IGNORE_NEW_LINES) as $line) { - - if (substr_count($line, ":") !== 2) - throw new DAV\Exception('Malformed htdigest file. Every line should contain 2 colons'); - - list($username,$realm,$A1) = explode(':',$line); - - if (!preg_match('/^[a-zA-Z0-9]{32}$/', $A1)) - throw new DAV\Exception('Malformed htdigest file. Invalid md5 hash'); - - $this->users[$realm . ':' . $username] = $A1; - - } - - } - - /** - * Returns a users' information - * - * @param string $realm - * @param string $username - * @return string - */ - public function getDigestHash($realm, $username) { - - return isset($this->users[$realm . ':' . $username])?$this->users[$realm . ':' . $username]:false; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/PDO.php b/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/PDO.php deleted file mode 100644 index f153d8429..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/PDO.php +++ /dev/null @@ -1,65 +0,0 @@ -pdo = $pdo; - $this->tableName = $tableName; - - } - - /** - * Returns the digest hash for a user. - * - * @param string $realm - * @param string $username - * @return string|null - */ - public function getDigestHash($realm,$username) { - - $stmt = $this->pdo->prepare('SELECT username, digesta1 FROM '.$this->tableName.' WHERE username = ?'); - $stmt->execute(array($username)); - $result = $stmt->fetchAll(); - - if (!count($result)) return; - - return $result[0]['digesta1']; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Auth/Plugin.php b/vendor/sabre/dav/lib/Sabre/DAV/Auth/Plugin.php deleted file mode 100644 index dbebc20f0..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Auth/Plugin.php +++ /dev/null @@ -1,112 +0,0 @@ -authBackend = $authBackend; - $this->realm = $realm; - - } - - /** - * Initializes the plugin. This function is automatically called by the server - * - * @param DAV\Server $server - * @return void - */ - public function initialize(DAV\Server $server) { - - $this->server = $server; - $this->server->subscribeEvent('beforeMethod',array($this,'beforeMethod'),10); - - } - - /** - * Returns a plugin name. - * - * Using this name other plugins will be able to access other plugins - * using DAV\Server::getPlugin - * - * @return string - */ - public function getPluginName() { - - return 'auth'; - - } - - /** - * Returns the current users' principal uri. - * - * If nobody is logged in, this will return null. - * - * @return string|null - */ - public function getCurrentUser() { - - $userInfo = $this->authBackend->getCurrentUser(); - if (!$userInfo) return null; - - return $userInfo; - - } - - /** - * This method is called before any HTTP method and forces users to be authenticated - * - * @param string $method - * @param string $uri - * @throws Sabre\DAV\Exception\NotAuthenticated - * @return bool - */ - public function beforeMethod($method, $uri) { - - $this->authBackend->authenticate($this->server,$this->realm); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Browser/GuessContentType.php b/vendor/sabre/dav/lib/Sabre/DAV/Browser/GuessContentType.php deleted file mode 100644 index 9fd47b930..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Browser/GuessContentType.php +++ /dev/null @@ -1,99 +0,0 @@ - 'image/jpeg', - 'gif' => 'image/gif', - 'png' => 'image/png', - - // groupware - 'ics' => 'text/calendar', - 'vcf' => 'text/x-vcard', - - // text - 'txt' => 'text/plain', - - ); - - /** - * Initializes the plugin - * - * @param DAV\Server $server - * @return void - */ - public function initialize(DAV\Server $server) { - - // Using a relatively low priority (200) to allow other extensions - // to set the content-type first. - $server->subscribeEvent('afterGetProperties',array($this,'afterGetProperties'),200); - - } - - /** - * Handler for teh afterGetProperties event - * - * @param string $path - * @param array $properties - * @return void - */ - public function afterGetProperties($path, &$properties) { - - if (array_key_exists('{DAV:}getcontenttype', $properties[404])) { - - list(, $fileName) = DAV\URLUtil::splitPath($path); - $contentType = $this->getContentType($fileName); - - if ($contentType) { - $properties[200]['{DAV:}getcontenttype'] = $contentType; - unset($properties[404]['{DAV:}getcontenttype']); - } - - } - - } - - /** - * Simple method to return the contenttype - * - * @param string $fileName - * @return string - */ - protected function getContentType($fileName) { - - // Just grabbing the extension - $extension = strtolower(substr($fileName,strrpos($fileName,'.')+1)); - if (isset($this->extensionMap[$extension])) - return $this->extensionMap[$extension]; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Browser/MapGetToPropFind.php b/vendor/sabre/dav/lib/Sabre/DAV/Browser/MapGetToPropFind.php deleted file mode 100644 index 881c063b9..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Browser/MapGetToPropFind.php +++ /dev/null @@ -1,57 +0,0 @@ -server = $server; - $this->server->subscribeEvent('beforeMethod',array($this,'httpGetInterceptor')); - } - - /** - * This method intercepts GET requests to non-files, and changes it into an HTTP PROPFIND request - * - * @param string $method - * @param string $uri - * @return bool - */ - public function httpGetInterceptor($method, $uri) { - - if ($method!='GET') return true; - - $node = $this->server->tree->getNodeForPath($uri); - if ($node instanceof DAV\IFile) return; - - $this->server->invokeMethod('PROPFIND',$uri); - return false; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Browser/Plugin.php b/vendor/sabre/dav/lib/Sabre/DAV/Browser/Plugin.php deleted file mode 100644 index 751c22965..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Browser/Plugin.php +++ /dev/null @@ -1,491 +0,0 @@ - 'icons/file', - 'Sabre\\DAV\\ICollection' => 'icons/collection', - 'Sabre\\DAVACL\\IPrincipal' => 'icons/principal', - 'Sabre\\CalDAV\\ICalendar' => 'icons/calendar', - 'Sabre\\CardDAV\\IAddressBook' => 'icons/addressbook', - 'Sabre\\CardDAV\\ICard' => 'icons/card', - ); - - /** - * The file extension used for all icons - * - * @var string - */ - public $iconExtension = '.png'; - - /** - * reference to server class - * - * @var Sabre\DAV\Server - */ - protected $server; - - /** - * enablePost turns on the 'actions' panel, which allows people to create - * folders and upload files straight from a browser. - * - * @var bool - */ - protected $enablePost = true; - - /** - * By default the browser plugin will generate a favicon and other images. - * To turn this off, set this property to false. - * - * @var bool - */ - protected $enableAssets = true; - - /** - * Creates the object. - * - * By default it will allow file creation and uploads. - * Specify the first argument as false to disable this - * - * @param bool $enablePost - * @param bool $enableAssets - */ - public function __construct($enablePost=true, $enableAssets = true) { - - $this->enablePost = $enablePost; - $this->enableAssets = $enableAssets; - - } - - /** - * Initializes the plugin and subscribes to events - * - * @param DAV\Server $server - * @return void - */ - public function initialize(DAV\Server $server) { - - $this->server = $server; - $this->server->subscribeEvent('beforeMethod',array($this,'httpGetInterceptor')); - $this->server->subscribeEvent('onHTMLActionsPanel', array($this, 'htmlActionsPanel'),200); - if ($this->enablePost) $this->server->subscribeEvent('unknownMethod',array($this,'httpPOSTHandler')); - } - - /** - * This method intercepts GET requests to collections and returns the html - * - * @param string $method - * @param string $uri - * @return bool - */ - public function httpGetInterceptor($method, $uri) { - - if ($method !== 'GET') return true; - - // We're not using straight-up $_GET, because we want everything to be - // unit testable. - $getVars = array(); - parse_str($this->server->httpRequest->getQueryString(), $getVars); - - if (isset($getVars['sabreAction']) && $getVars['sabreAction'] === 'asset' && isset($getVars['assetName'])) { - $this->serveAsset($getVars['assetName']); - return false; - } - - try { - $node = $this->server->tree->getNodeForPath($uri); - } catch (DAV\Exception\NotFound $e) { - // We're simply stopping when the file isn't found to not interfere - // with other plugins. - return; - } - if ($node instanceof DAV\IFile) - return; - - $this->server->httpResponse->sendStatus(200); - $this->server->httpResponse->setHeader('Content-Type','text/html; charset=utf-8'); - - $this->server->httpResponse->sendBody( - $this->generateDirectoryIndex($uri) - ); - - return false; - - } - - /** - * Handles POST requests for tree operations. - * - * @param string $method - * @param string $uri - * @return bool - */ - public function httpPOSTHandler($method, $uri) { - - if ($method!='POST') return; - $contentType = $this->server->httpRequest->getHeader('Content-Type'); - list($contentType) = explode(';', $contentType); - if ($contentType !== 'application/x-www-form-urlencoded' && - $contentType !== 'multipart/form-data') { - return; - } - $postVars = $this->server->httpRequest->getPostVars(); - - if (!isset($postVars['sabreAction'])) - return; - - if ($this->server->broadcastEvent('onBrowserPostAction', array($uri, $postVars['sabreAction'], $postVars))) { - - switch($postVars['sabreAction']) { - - case 'mkcol' : - if (isset($postVars['name']) && trim($postVars['name'])) { - // Using basename() because we won't allow slashes - list(, $folderName) = DAV\URLUtil::splitPath(trim($postVars['name'])); - $this->server->createDirectory($uri . '/' . $folderName); - } - break; - case 'put' : - if ($_FILES) $file = current($_FILES); - else break; - - list(, $newName) = DAV\URLUtil::splitPath(trim($file['name'])); - if (isset($postVars['name']) && trim($postVars['name'])) - $newName = trim($postVars['name']); - - // Making sure we only have a 'basename' component - list(, $newName) = DAV\URLUtil::splitPath($newName); - - if (is_uploaded_file($file['tmp_name'])) { - $this->server->createFile($uri . '/' . $newName, fopen($file['tmp_name'],'r')); - } - break; - - } - - } - $this->server->httpResponse->setHeader('Location',$this->server->httpRequest->getUri()); - $this->server->httpResponse->sendStatus(302); - return false; - - } - - /** - * Escapes a string for html. - * - * @param string $value - * @return string - */ - public function escapeHTML($value) { - - return htmlspecialchars($value,ENT_QUOTES,'UTF-8'); - - } - - /** - * Generates the html directory index for a given url - * - * @param string $path - * @return string - */ - public function generateDirectoryIndex($path) { - - $version = ''; - if (DAV\Server::$exposeVersion) { - $version = DAV\Version::VERSION ."-". DAV\Version::STABILITY; - } - - $html = " - - Index for " . $this->escapeHTML($path) . "/ - SabreDAV " . $version . " - - "; - - if ($this->enableAssets) { - $html.=''; - } - - $html .= " - -

    Index for " . $this->escapeHTML($path) . "/

    - - - "; - - $files = $this->server->getPropertiesForPath($path,array( - '{DAV:}displayname', - '{DAV:}resourcetype', - '{DAV:}getcontenttype', - '{DAV:}getcontentlength', - '{DAV:}getlastmodified', - ),1); - - $parent = $this->server->tree->getNodeForPath($path); - - - if ($path) { - - list($parentUri) = DAV\URLUtil::splitPath($path); - $fullPath = DAV\URLUtil::encodePath($this->server->getBaseUri() . $parentUri); - - $icon = $this->enableAssets?'Parent':''; - $html.= " - - - - - - "; - - } - - foreach($files as $file) { - - // This is the current directory, we can skip it - if (rtrim($file['href'],'/')==$path) continue; - - list(, $name) = DAV\URLUtil::splitPath($file['href']); - - $type = null; - - - if (isset($file[200]['{DAV:}resourcetype'])) { - $type = $file[200]['{DAV:}resourcetype']->getValue(); - - // resourcetype can have multiple values - if (!is_array($type)) $type = array($type); - - foreach($type as $k=>$v) { - - // Some name mapping is preferred - switch($v) { - case '{DAV:}collection' : - $type[$k] = 'Collection'; - break; - case '{DAV:}principal' : - $type[$k] = 'Principal'; - break; - case '{urn:ietf:params:xml:ns:carddav}addressbook' : - $type[$k] = 'Addressbook'; - break; - case '{urn:ietf:params:xml:ns:caldav}calendar' : - $type[$k] = 'Calendar'; - break; - case '{urn:ietf:params:xml:ns:caldav}schedule-inbox' : - $type[$k] = 'Schedule Inbox'; - break; - case '{urn:ietf:params:xml:ns:caldav}schedule-outbox' : - $type[$k] = 'Schedule Outbox'; - break; - case '{http://calendarserver.org/ns/}calendar-proxy-read' : - $type[$k] = 'Proxy-Read'; - break; - case '{http://calendarserver.org/ns/}calendar-proxy-write' : - $type[$k] = 'Proxy-Write'; - break; - } - - } - $type = implode(', ', $type); - } - - // If no resourcetype was found, we attempt to use - // the contenttype property - if (!$type && isset($file[200]['{DAV:}getcontenttype'])) { - $type = $file[200]['{DAV:}getcontenttype']; - } - if (!$type) $type = 'Unknown'; - - $size = isset($file[200]['{DAV:}getcontentlength'])?(int)$file[200]['{DAV:}getcontentlength']:''; - $lastmodified = isset($file[200]['{DAV:}getlastmodified'])?$file[200]['{DAV:}getlastmodified']->getTime()->format(\DateTime::ATOM):''; - - $fullPath = DAV\URLUtil::encodePath('/' . trim($this->server->getBaseUri() . ($path?$path . '/':'') . $name,'/')); - - $displayName = isset($file[200]['{DAV:}displayname'])?$file[200]['{DAV:}displayname']:$name; - - $displayName = $this->escapeHTML($displayName); - $type = $this->escapeHTML($type); - - $icon = ''; - - if ($this->enableAssets) { - $node = $this->server->tree->getNodeForPath(($path?$path.'/':'') . $name); - foreach(array_reverse($this->iconMap) as $class=>$iconName) { - - if ($node instanceof $class) { - $icon = ''; - break; - } - - - } - - } - - $html.= " - - - - - - "; - - } - - $html.= ""; - - $output = ''; - - if ($this->enablePost) { - $this->server->broadcastEvent('onHTMLActionsPanel',array($parent, &$output)); - } - - $html.=$output; - - $html.= "
    NameTypeSizeLast modified

    $icon..[parent]
    $icon{$displayName}{$type}{$size}{$lastmodified}

    -
    Generated by SabreDAV " . $version . " (c)2007-2014 http://sabre.io/
    - - "; - - return $html; - - } - - /** - * This method is used to generate the 'actions panel' output for - * collections. - * - * This specifically generates the interfaces for creating new files, and - * creating new directories. - * - * @param DAV\INode $node - * @param mixed $output - * @return void - */ - public function htmlActionsPanel(DAV\INode $node, &$output) { - - if (!$node instanceof DAV\ICollection) - return; - - // We also know fairly certain that if an object is a non-extended - // SimpleCollection, we won't need to show the panel either. - if (get_class($node)==='Sabre\\DAV\\SimpleCollection') - return; - - $output.= '
    -

    Create new folder

    - - Name:
    - -
    -
    -

    Upload file

    - - Name (optional):
    - File:
    - -
    - '; - - } - - /** - * This method takes a path/name of an asset and turns it into url - * suiteable for http access. - * - * @param string $assetName - * @return string - */ - protected function getAssetUrl($assetName) { - - return $this->server->getBaseUri() . '?sabreAction=asset&assetName=' . urlencode($assetName); - - } - - /** - * This method returns a local pathname to an asset. - * - * @param string $assetName - * @return string - */ - protected function getLocalAssetPath($assetName) { - - $assetDir = __DIR__ . '/assets/'; - $path = $assetDir . $assetName; - - // Making sure people aren't trying to escape from the base path. - if (strpos(realpath($path), realpath($assetDir)) === 0) { - return $path; - } - throw new DAV\Exception\Forbidden('Path does not exist, or escaping from the base path was detected'); - } - - /** - * This method reads an asset from disk and generates a full http response. - * - * @param string $assetName - * @return void - */ - protected function serveAsset($assetName) { - - $assetPath = $this->getLocalAssetPath($assetName); - if (!file_exists($assetPath)) { - throw new DAV\Exception\NotFound('Could not find an asset with this name'); - } - // Rudimentary mime type detection - switch(strtolower(substr($assetPath,strpos($assetPath,'.')+1))) { - - case 'ico' : - $mime = 'image/vnd.microsoft.icon'; - break; - - case 'png' : - $mime = 'image/png'; - break; - - default: - $mime = 'application/octet-stream'; - break; - - } - - $this->server->httpResponse->setHeader('Content-Type', $mime); - $this->server->httpResponse->setHeader('Content-Length', filesize($assetPath)); - $this->server->httpResponse->setHeader('Cache-Control', 'public, max-age=1209600'); - $this->server->httpResponse->sendStatus(200); - $this->server->httpResponse->sendBody(fopen($assetPath,'r')); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/addressbook.png b/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/addressbook.png deleted file mode 100644 index c9acc8417..000000000 Binary files a/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/addressbook.png and /dev/null differ diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/calendar.png b/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/calendar.png deleted file mode 100644 index 3ecd6a800..000000000 Binary files a/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/calendar.png and /dev/null differ diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/card.png b/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/card.png deleted file mode 100644 index 2ce954866..000000000 Binary files a/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/card.png and /dev/null differ diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/collection.png b/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/collection.png deleted file mode 100644 index 156fa64fd..000000000 Binary files a/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/collection.png and /dev/null differ diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/file.png b/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/file.png deleted file mode 100644 index 3b98551ce..000000000 Binary files a/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/file.png and /dev/null differ diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/parent.png b/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/parent.png deleted file mode 100644 index 156fa64fd..000000000 Binary files a/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/parent.png and /dev/null differ diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/principal.png b/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/principal.png deleted file mode 100644 index f8988f828..000000000 Binary files a/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/principal.png and /dev/null differ diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Client.php b/vendor/sabre/dav/lib/Sabre/DAV/Client.php deleted file mode 100644 index 705b32195..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Client.php +++ /dev/null @@ -1,575 +0,0 @@ -$validSetting = $settings[$validSetting]; - } - } - - if (isset($settings['authType'])) { - $this->authType = $settings['authType']; - } else { - $this->authType = self::AUTH_BASIC | self::AUTH_DIGEST; - } - - $this->propertyMap['{DAV:}resourcetype'] = 'Sabre\\DAV\\Property\\ResourceType'; - - } - - /** - * Add trusted root certificates to the webdav client. - * - * The parameter certificates should be a absolute path to a file - * which contains all trusted certificates - * - * @param string $certificates - */ - public function addTrustedCertificates($certificates) { - $this->trustedCertificates = $certificates; - } - - /** - * Enables/disables SSL peer verification - * - * @param boolean $value - */ - public function setVerifyPeer($value) { - $this->verifyPeer = $value; - } - - /** - * Does a PROPFIND request - * - * The list of requested properties must be specified as an array, in clark - * notation. - * - * The returned array will contain a list of filenames as keys, and - * properties as values. - * - * The properties array will contain the list of properties. Only properties - * that are actually returned from the server (without error) will be - * returned, anything else is discarded. - * - * Depth should be either 0 or 1. A depth of 1 will cause a request to be - * made to the server to also return all child resources. - * - * @param string $url - * @param array $properties - * @param int $depth - * @return array - */ - public function propFind($url, array $properties, $depth = 0) { - - $body = '' . "\n"; - $body.= '' . "\n"; - $body.= ' ' . "\n"; - - foreach($properties as $property) { - - list( - $namespace, - $elementName - ) = XMLUtil::parseClarkNotation($property); - - if ($namespace === 'DAV:') { - $body.=' ' . "\n"; - } else { - $body.=" \n"; - } - - } - - $body.= ' ' . "\n"; - $body.= ''; - - $response = $this->request('PROPFIND', $url, $body, array( - 'Depth' => $depth, - 'Content-Type' => 'application/xml' - )); - - $result = $this->parseMultiStatus($response['body']); - - // If depth was 0, we only return the top item - if ($depth===0) { - reset($result); - $result = current($result); - return isset($result[200])?$result[200]:array(); - } - - $newResult = array(); - foreach($result as $href => $statusList) { - - $newResult[$href] = isset($statusList[200])?$statusList[200]:array(); - - } - - return $newResult; - - } - - /** - * Updates a list of properties on the server - * - * The list of properties must have clark-notation properties for the keys, - * and the actual (string) value for the value. If the value is null, an - * attempt is made to delete the property. - * - * @todo Must be building the request using the DOM, and does not yet - * support complex properties. - * @param string $url - * @param array $properties - * @return void - */ - public function propPatch($url, array $properties) { - - $body = '' . "\n"; - $body.= '' . "\n"; - - foreach($properties as $propName => $propValue) { - - list( - $namespace, - $elementName - ) = XMLUtil::parseClarkNotation($propName); - - if ($propValue === null) { - - $body.="\n"; - - if ($namespace === 'DAV:') { - $body.=' ' . "\n"; - } else { - $body.=" \n"; - } - - $body.="\n"; - - } else { - - $body.="\n"; - if ($namespace === 'DAV:') { - $body.=' '; - } else { - $body.=" "; - } - // Shitty.. i know - $body.=htmlspecialchars($propValue, ENT_NOQUOTES, 'UTF-8'); - if ($namespace === 'DAV:') { - $body.='' . "\n"; - } else { - $body.="\n"; - } - $body.="\n"; - - } - - } - - $body.= ''; - - $this->request('PROPPATCH', $url, $body, array( - 'Content-Type' => 'application/xml' - )); - - } - - /** - * Performs an HTTP options request - * - * This method returns all the features from the 'DAV:' header as an array. - * If there was no DAV header, or no contents this method will return an - * empty array. - * - * @return array - */ - public function options() { - - $result = $this->request('OPTIONS'); - if (!isset($result['headers']['dav'])) { - return array(); - } - - $features = explode(',', $result['headers']['dav']); - foreach($features as &$v) { - $v = trim($v); - } - return $features; - - } - - /** - * Performs an actual HTTP request, and returns the result. - * - * If the specified url is relative, it will be expanded based on the base - * url. - * - * The returned array contains 3 keys: - * * body - the response body - * * httpCode - a HTTP code (200, 404, etc) - * * headers - a list of response http headers. The header names have - * been lowercased. - * - * @param string $method - * @param string $url - * @param string $body - * @param array $headers - * @return array - */ - public function request($method, $url = '', $body = null, $headers = array()) { - - $url = $this->getAbsoluteUrl($url); - - $curlSettings = array( - CURLOPT_RETURNTRANSFER => true, - // Return headers as part of the response - CURLOPT_HEADER => true, - - // For security we cast this to a string. If somehow an array could - // be passed here, it would be possible for an attacker to use @ to - // post local files. - CURLOPT_POSTFIELDS => (string)$body, - // Automatically follow redirects - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_MAXREDIRS => 5, - ); - - if($this->verifyPeer !== null) { - $curlSettings[CURLOPT_SSL_VERIFYPEER] = $this->verifyPeer; - } - - if($this->trustedCertificates) { - $curlSettings[CURLOPT_CAINFO] = $this->trustedCertificates; - } - - switch ($method) { - case 'HEAD' : - - // do not read body with HEAD requests (this is necessary because cURL does not ignore the body with HEAD - // requests when the Content-Length header is given - which in turn is perfectly valid according to HTTP - // specs...) cURL does unfortunately return an error in this case ("transfer closed transfer closed with - // ... bytes remaining to read") this can be circumvented by explicitly telling cURL to ignore the - // response body - $curlSettings[CURLOPT_NOBODY] = true; - $curlSettings[CURLOPT_CUSTOMREQUEST] = 'HEAD'; - break; - - default: - $curlSettings[CURLOPT_CUSTOMREQUEST] = $method; - break; - - } - - // Adding HTTP headers - $nHeaders = array(); - foreach($headers as $key=>$value) { - - $nHeaders[] = $key . ': ' . $value; - - } - $curlSettings[CURLOPT_HTTPHEADER] = $nHeaders; - - if ($this->proxy) { - $curlSettings[CURLOPT_PROXY] = $this->proxy; - } - - if ($this->userName && $this->authType) { - $curlType = 0; - if ($this->authType & self::AUTH_BASIC) { - $curlType |= CURLAUTH_BASIC; - } - if ($this->authType & self::AUTH_DIGEST) { - $curlType |= CURLAUTH_DIGEST; - } - $curlSettings[CURLOPT_HTTPAUTH] = $curlType; - $curlSettings[CURLOPT_USERPWD] = $this->userName . ':' . $this->password; - } - - list( - $response, - $curlInfo, - $curlErrNo, - $curlError - ) = $this->curlRequest($url, $curlSettings); - - $headerBlob = substr($response, 0, $curlInfo['header_size']); - $response = substr($response, $curlInfo['header_size']); - - // In the case of 100 Continue, or redirects we'll have multiple lists - // of headers for each separate HTTP response. We can easily split this - // because they are separated by \r\n\r\n - $headerBlob = explode("\r\n\r\n", trim($headerBlob, "\r\n")); - - // We only care about the last set of headers - $headerBlob = $headerBlob[count($headerBlob)-1]; - - // Splitting headers - $headerBlob = explode("\r\n", $headerBlob); - - $headers = array(); - foreach($headerBlob as $header) { - $parts = explode(':', $header, 2); - if (count($parts)==2) { - $headers[strtolower(trim($parts[0]))] = trim($parts[1]); - } - } - - $response = array( - 'body' => $response, - 'statusCode' => $curlInfo['http_code'], - 'headers' => $headers - ); - - if ($curlErrNo) { - throw new Exception('[CURL] Error while making request: ' . $curlError . ' (error code: ' . $curlErrNo . ')'); - } - - if ($response['statusCode']>=400) { - switch ($response['statusCode']) { - case 400 : - throw new Exception\BadRequest('Bad request'); - case 401 : - throw new Exception\NotAuthenticated('Not authenticated'); - case 402 : - throw new Exception\PaymentRequired('Payment required'); - case 403 : - throw new Exception\Forbidden('Forbidden'); - case 404: - throw new Exception\NotFound('Resource not found.'); - case 405 : - throw new Exception\MethodNotAllowed('Method not allowed'); - case 409 : - throw new Exception\Conflict('Conflict'); - case 412 : - throw new Exception\PreconditionFailed('Precondition failed'); - case 416 : - throw new Exception\RequestedRangeNotSatisfiable('Requested Range Not Satisfiable'); - case 500 : - throw new Exception('Internal server error'); - case 501 : - throw new Exception\NotImplemented('Not Implemented'); - case 507 : - throw new Exception\InsufficientStorage('Insufficient storage'); - default: - throw new Exception('HTTP error response. (errorcode ' . $response['statusCode'] . ')'); - } - } - - return $response; - - } - - /** - * Wrapper for all curl functions. - * - * The only reason this was split out in a separate method, is so it - * becomes easier to unittest. - * - * @param string $url - * @param array $settings - * @return array - */ - // @codeCoverageIgnoreStart - protected function curlRequest($url, $settings) { - - $curl = curl_init($url); - curl_setopt_array($curl, $settings); - - return array( - curl_exec($curl), - curl_getinfo($curl), - curl_errno($curl), - curl_error($curl) - ); - - } - // @codeCoverageIgnoreEnd - - /** - * Returns the full url based on the given url (which may be relative). All - * urls are expanded based on the base url as given by the server. - * - * @param string $url - * @return string - */ - protected function getAbsoluteUrl($url) { - - // If the url starts with http:// or https://, the url is already absolute. - if (preg_match('/^http(s?):\/\//', $url)) { - return $url; - } - - // If the url starts with a slash, we must calculate the url based off - // the root of the base url. - if (strpos($url,'/') === 0) { - $parts = parse_url($this->baseUri); - return $parts['scheme'] . '://' . $parts['host'] . (isset($parts['port'])?':' . $parts['port']:'') . $url; - } - - // Otherwise... - return $this->baseUri . $url; - - } - - /** - * Parses a WebDAV multistatus response body - * - * This method returns an array with the following structure - * - * array( - * 'url/to/resource' => array( - * '200' => array( - * '{DAV:}property1' => 'value1', - * '{DAV:}property2' => 'value2', - * ), - * '404' => array( - * '{DAV:}property1' => null, - * '{DAV:}property2' => null, - * ), - * ) - * 'url/to/resource2' => array( - * .. etc .. - * ) - * ) - * - * - * @param string $body xml body - * @return array - */ - public function parseMultiStatus($body) { - - $body = XMLUtil::convertDAVNamespace($body); - - // Fixes an XXE vulnerability on PHP versions older than 5.3.23 or - // 5.4.13. - $previous = libxml_disable_entity_loader(true); - $responseXML = simplexml_load_string($body, null, LIBXML_NOBLANKS | LIBXML_NOCDATA); - libxml_disable_entity_loader($previous); - - if ($responseXML===false) { - throw new \InvalidArgumentException('The passed data is not valid XML'); - } - - $responseXML->registerXPathNamespace('d', 'urn:DAV'); - - $propResult = array(); - - foreach($responseXML->xpath('d:response') as $response) { - $response->registerXPathNamespace('d', 'urn:DAV'); - $href = $response->xpath('d:href'); - $href = (string)$href[0]; - - $properties = array(); - - foreach($response->xpath('d:propstat') as $propStat) { - - $propStat->registerXPathNamespace('d', 'urn:DAV'); - $status = $propStat->xpath('d:status'); - list($httpVersion, $statusCode, $message) = explode(' ', (string)$status[0],3); - - // Only using the propertymap for results with status 200. - $propertyMap = $statusCode==='200' ? $this->propertyMap : array(); - - $properties[$statusCode] = XMLUtil::parseProperties(dom_import_simplexml($propStat), $propertyMap); - - } - - $propResult[$href] = $properties; - - } - - return $propResult; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Collection.php b/vendor/sabre/dav/lib/Sabre/DAV/Collection.php deleted file mode 100644 index 0090a4d6e..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Collection.php +++ /dev/null @@ -1,110 +0,0 @@ -getChildren() as $child) { - - if ($child->getName()==$name) return $child; - - } - throw new Exception\NotFound('File not found: ' . $name); - - } - - /** - * Checks is a child-node exists. - * - * It is generally a good idea to try and override this. Usually it can be optimized. - * - * @param string $name - * @return bool - */ - public function childExists($name) { - - try { - - $this->getChild($name); - return true; - - } catch(Exception\NotFound $e) { - - return false; - - } - - } - - /** - * Creates a new file in the directory - * - * Data will either be supplied as a stream resource, or in certain cases - * as a string. Keep in mind that you may have to support either. - * - * After succesful creation of the file, you may choose to return the ETag - * of the new file here. - * - * The returned ETag must be surrounded by double-quotes (The quotes should - * be part of the actual string). - * - * If you cannot accurately determine the ETag, you should not return it. - * If you don't store the file exactly as-is (you're transforming it - * somehow) you should also not return an ETag. - * - * This means that if a subsequent GET to this new file does not exactly - * return the same contents of what was submitted here, you are strongly - * recommended to omit the ETag. - * - * @param string $name Name of the file - * @param resource|string $data Initial payload - * @return null|string - */ - public function createFile($name, $data = null) { - - throw new Exception\Forbidden('Permission denied to create file (filename ' . $name . ')'); - - } - - /** - * Creates a new subdirectory - * - * @param string $name - * @throws Exception\Forbidden - * @return void - */ - public function createDirectory($name) { - - throw new Exception\Forbidden('Permission denied to create directory'); - - } - - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception.php deleted file mode 100644 index 22a319e9f..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Exception.php +++ /dev/null @@ -1,64 +0,0 @@ -lock) { - $error = $errorNode->ownerDocument->createElementNS('DAV:','d:no-conflicting-lock'); - $errorNode->appendChild($error); - if (!is_object($this->lock)) var_dump($this->lock); - $error->appendChild($errorNode->ownerDocument->createElementNS('DAV:','d:href',$this->lock->uri)); - } - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception/FileNotFound.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/FileNotFound.php deleted file mode 100644 index aa4844cb9..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Exception/FileNotFound.php +++ /dev/null @@ -1,19 +0,0 @@ -ownerDocument->createElementNS('DAV:','d:valid-resourcetype'); - $errorNode->appendChild($error); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception/LengthRequired.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/LengthRequired.php deleted file mode 100644 index 9487686dc..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Exception/LengthRequired.php +++ /dev/null @@ -1,30 +0,0 @@ -message = 'The locktoken supplied does not match any locks on this entity'; - - } - - /** - * This method allows the exception to include additional information into the WebDAV error response - * - * @param DAV\Server $server - * @param \DOMElement $errorNode - * @return void - */ - public function serialize(DAV\Server $server,\DOMElement $errorNode) { - - $error = $errorNode->ownerDocument->createElementNS('DAV:','d:lock-token-matches-request-uri'); - $errorNode->appendChild($error); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception/Locked.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/Locked.php deleted file mode 100644 index 2bee1b02f..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Exception/Locked.php +++ /dev/null @@ -1,73 +0,0 @@ -lock = $lock; - - } - - /** - * Returns the HTTP statuscode for this exception - * - * @return int - */ - public function getHTTPCode() { - - return 423; - - } - - /** - * This method allows the exception to include additional information into the WebDAV error response - * - * @param DAV\Server $server - * @param \DOMElement $errorNode - * @return void - */ - public function serialize(DAV\Server $server,\DOMElement $errorNode) { - - if ($this->lock) { - $error = $errorNode->ownerDocument->createElementNS('DAV:','d:lock-token-submitted'); - $errorNode->appendChild($error); - - $href = $errorNode->ownerDocument->createElementNS('DAV:','d:href'); - $href->appendChild($errorNode->ownerDocument->createTextNode($this->lock->uri)); - $error->appendChild( - $href - ); - } - - } - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception/MethodNotAllowed.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/MethodNotAllowed.php deleted file mode 100644 index 05970cfa8..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Exception/MethodNotAllowed.php +++ /dev/null @@ -1,45 +0,0 @@ -getAllowedMethods($server->getRequestUri()); - - return array( - 'Allow' => strtoupper(implode(', ',$methods)), - ); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception/NotAuthenticated.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/NotAuthenticated.php deleted file mode 100644 index c082d489b..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Exception/NotAuthenticated.php +++ /dev/null @@ -1,30 +0,0 @@ -header = $header; - - } - - /** - * Returns the HTTP statuscode for this exception - * - * @return int - */ - public function getHTTPCode() { - - return 412; - - } - - /** - * This method allows the exception to include additional information into the WebDAV error response - * - * @param DAV\Server $server - * @param \DOMElement $errorNode - * @return void - */ - public function serialize(DAV\Server $server,\DOMElement $errorNode) { - - if ($this->header) { - $prop = $errorNode->ownerDocument->createElement('s:header'); - $prop->nodeValue = $this->header; - $errorNode->appendChild($prop); - } - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception/ReportNotSupported.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/ReportNotSupported.php deleted file mode 100644 index 8e32096e0..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Exception/ReportNotSupported.php +++ /dev/null @@ -1,32 +0,0 @@ -ownerDocument->createElementNS('DAV:','d:supported-report'); - $errorNode->appendChild($error); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception/RequestedRangeNotSatisfiable.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/RequestedRangeNotSatisfiable.php deleted file mode 100644 index 25002be6a..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Exception/RequestedRangeNotSatisfiable.php +++ /dev/null @@ -1,31 +0,0 @@ - - * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/). - * @license http://sabre.io/license/ Modified BSD License - */ -class ServiceUnavailable extends DAV\Exception { - - /** - * Returns the HTTP statuscode for this exception - * - * @return int - */ - public function getHTTPCode() { - - return 503; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception/UnsupportedMediaType.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/UnsupportedMediaType.php deleted file mode 100644 index 46eea60df..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Exception/UnsupportedMediaType.php +++ /dev/null @@ -1,28 +0,0 @@ -path . '/' . $name; - file_put_contents($newPath,$data); - - } - - /** - * Creates a new subdirectory - * - * @param string $name - * @return void - */ - public function createDirectory($name) { - - $newPath = $this->path . '/' . $name; - mkdir($newPath); - - } - - /** - * Returns a specific child node, referenced by its name - * - * This method must throw DAV\Exception\NotFound if the node does not - * exist. - * - * @param string $name - * @throws DAV\Exception\NotFound - * @return DAV\INode - */ - public function getChild($name) { - - $path = $this->path . '/' . $name; - - if (!file_exists($path)) throw new DAV\Exception\NotFound('File with name ' . $path . ' could not be located'); - - if (is_dir($path)) { - - return new Directory($path); - - } else { - - return new File($path); - - } - - } - - /** - * Returns an array with all the child nodes - * - * @return DAV\INode[] - */ - public function getChildren() { - - $nodes = array(); - foreach(scandir($this->path) as $node) if($node!='.' && $node!='..') $nodes[] = $this->getChild($node); - return $nodes; - - } - - /** - * Checks if a child exists. - * - * @param string $name - * @return bool - */ - public function childExists($name) { - - $path = $this->path . '/' . $name; - return file_exists($path); - - } - - /** - * Deletes all files in this directory, and then itself - * - * @return void - */ - public function delete() { - - foreach($this->getChildren() as $child) $child->delete(); - rmdir($this->path); - - } - - /** - * Returns available diskspace information - * - * @return array - */ - public function getQuotaInfo() { - - return array( - disk_total_space($this->path)-disk_free_space($this->path), - disk_free_space($this->path) - ); - - } - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAV/FS/File.php b/vendor/sabre/dav/lib/Sabre/DAV/FS/File.php deleted file mode 100644 index d10370fae..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/FS/File.php +++ /dev/null @@ -1,91 +0,0 @@ -path,$data); - - } - - /** - * Returns the data - * - * @return string - */ - public function get() { - - return fopen($this->path,'r'); - - } - - /** - * Delete the current file - * - * @return void - */ - public function delete() { - - unlink($this->path); - - } - - /** - * Returns the size of the node, in bytes - * - * @return int - */ - public function getSize() { - - return filesize($this->path); - - } - - /** - * Returns the ETag for a file - * - * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. - * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. - * - * Return null if the ETag can not effectively be determined - * - * @return mixed - */ - public function getETag() { - - return null; - - } - - /** - * Returns the mime-type for a file - * - * If null is returned, we'll assume application/octet-stream - * - * @return mixed - */ - public function getContentType() { - - return null; - - } - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAV/FS/Node.php b/vendor/sabre/dav/lib/Sabre/DAV/FS/Node.php deleted file mode 100644 index 605fa3c82..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/FS/Node.php +++ /dev/null @@ -1,82 +0,0 @@ -path = $path; - - } - - - - /** - * Returns the name of the node - * - * @return string - */ - public function getName() { - - list(, $name) = DAV\URLUtil::splitPath($this->path); - return $name; - - } - - /** - * Renames the node - * - * @param string $name The new name - * @return void - */ - public function setName($name) { - - list($parentPath, ) = DAV\URLUtil::splitPath($this->path); - list(, $newName) = DAV\URLUtil::splitPath($name); - - $newPath = $parentPath . '/' . $newName; - rename($this->path,$newPath); - - $this->path = $newPath; - - } - - - - /** - * Returns the last modification time, as a unix timestamp - * - * @return int - */ - public function getLastModified() { - - return filemtime($this->path); - - } - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAV/FSExt/Directory.php b/vendor/sabre/dav/lib/Sabre/DAV/FSExt/Directory.php deleted file mode 100644 index da3d2cc69..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/FSExt/Directory.php +++ /dev/null @@ -1,159 +0,0 @@ -path . '/' . $name; - file_put_contents($newPath,$data); - - return '"' . md5_file($newPath) . '"'; - - } - - /** - * Creates a new subdirectory - * - * @param string $name - * @return void - */ - public function createDirectory($name) { - - // We're not allowing dots - if ($name=='.' || $name=='..') throw new DAV\Exception\Forbidden('Permission denied to . and ..'); - $newPath = $this->path . '/' . $name; - mkdir($newPath); - - } - - /** - * Returns a specific child node, referenced by its name - * - * This method must throw Sabre\DAV\Exception\NotFound if the node does not - * exist. - * - * @param string $name - * @throws DAV\Exception\NotFound - * @return DAV\INode - */ - public function getChild($name) { - - $path = $this->path . '/' . $name; - - if (!file_exists($path)) throw new DAV\Exception\NotFound('File could not be located'); - if ($name=='.' || $name=='..') throw new DAV\Exception\Forbidden('Permission denied to . and ..'); - - if (is_dir($path)) { - - return new Directory($path); - - } else { - - return new File($path); - - } - - } - - /** - * Checks if a child exists. - * - * @param string $name - * @return bool - */ - public function childExists($name) { - - if ($name=='.' || $name=='..') - throw new DAV\Exception\Forbidden('Permission denied to . and ..'); - - $path = $this->path . '/' . $name; - return file_exists($path); - - } - - /** - * Returns an array with all the child nodes - * - * @return DAV\INode[] - */ - public function getChildren() { - - $nodes = array(); - foreach(scandir($this->path) as $node) if($node!='.' && $node!='..' && $node!='.sabredav') $nodes[] = $this->getChild($node); - return $nodes; - - } - - /** - * Deletes all files in this directory, and then itself - * - * @return bool - */ - public function delete() { - - // Deleting all children - foreach($this->getChildren() as $child) $child->delete(); - - // Removing resource info, if its still around - if (file_exists($this->path . '/.sabredav')) unlink($this->path . '/.sabredav'); - - // Removing the directory itself - rmdir($this->path); - - return parent::delete(); - - } - - /** - * Returns available diskspace information - * - * @return array - */ - public function getQuotaInfo() { - - return array( - disk_total_space($this->path)-disk_free_space($this->path), - disk_free_space($this->path) - ); - - } - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAV/FSExt/File.php b/vendor/sabre/dav/lib/Sabre/DAV/FSExt/File.php deleted file mode 100644 index 6588fad7e..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/FSExt/File.php +++ /dev/null @@ -1,146 +0,0 @@ -path,$data); - return '"' . md5_file($this->path) . '"'; - - } - - /** - * Updates the file based on a range specification. - * - * The first argument is the data, which is either a readable stream - * resource or a string. - * - * The second argument is the type of update we're doing. - * This is either: - * * 1. append - * * 2. update based on a start byte - * * 3. update based on an end byte - *; - * The third argument is the start or end byte. - * - * After a successful put operation, you may choose to return an ETag. The - * etag must always be surrounded by double-quotes. These quotes must - * appear in the actual string you're returning. - * - * Clients may use the ETag from a PUT request to later on make sure that - * when they update the file, the contents haven't changed in the mean - * time. - * - * @param resource|string $data - * @param int $rangeType - * @param int $offset - * @return string|null - */ - public function patch($data, $rangeType, $offset = null) { - - switch($rangeType) { - case 1 : - $f = fopen($this->path, 'a'); - break; - case 2 : - $f = fopen($this->path, 'c'); - fseek($f,$offset); - break; - case 3 : - $f = fopen($this->path, 'c'); - fseek($f, $offset, SEEK_END); - break; - } - if (is_string($data)) { - fwrite($f, $data); - } else { - stream_copy_to_stream($data,$f); - } - fclose($f); - return '"' . md5_file($this->path) . '"'; - - } - - /** - * Returns the data - * - * @return resource - */ - public function get() { - - return fopen($this->path,'r'); - - } - - /** - * Delete the current file - * - * @return bool - */ - public function delete() { - - unlink($this->path); - return parent::delete(); - - } - - /** - * Returns the ETag for a file - * - * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. - * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. - * - * Return null if the ETag can not effectively be determined - * - * @return string|null - */ - public function getETag() { - - return '"' . md5_file($this->path). '"'; - - } - - /** - * Returns the mime-type for a file - * - * If null is returned, we'll assume application/octet-stream - * - * @return string|null - */ - public function getContentType() { - - return null; - - } - - /** - * Returns the size of the file, in bytes - * - * @return int - */ - public function getSize() { - - return filesize($this->path); - - } - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAV/FSExt/Node.php b/vendor/sabre/dav/lib/Sabre/DAV/FSExt/Node.php deleted file mode 100644 index 0e11582f3..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/FSExt/Node.php +++ /dev/null @@ -1,214 +0,0 @@ -getResourceData(); - - foreach($properties as $propertyName=>$propertyValue) { - - // If it was null, we need to delete the property - if (is_null($propertyValue)) { - if (isset($resourceData['properties'][$propertyName])) { - unset($resourceData['properties'][$propertyName]); - } - } else { - $resourceData['properties'][$propertyName] = $propertyValue; - } - - } - - $this->putResourceData($resourceData); - return true; - } - - /** - * Returns a list of properties for this nodes.; - * - * The properties list is a list of propertynames the client requested, encoded as xmlnamespace#tagName, for example: http://www.example.org/namespace#author - * If the array is empty, all properties should be returned - * - * @param array $properties - * @return array - */ - function getProperties($properties) { - - $resourceData = $this->getResourceData(); - - // if the array was empty, we need to return everything - if (!$properties) return $resourceData['properties']; - - $props = array(); - foreach($properties as $property) { - if (isset($resourceData['properties'][$property])) $props[$property] = $resourceData['properties'][$property]; - } - - return $props; - - } - - /** - * Returns the path to the resource file - * - * @return string - */ - protected function getResourceInfoPath() { - - list($parentDir) = DAV\URLUtil::splitPath($this->path); - return $parentDir . '/.sabredav'; - - } - - /** - * Returns all the stored resource information - * - * @return array - */ - protected function getResourceData() { - - $path = $this->getResourceInfoPath(); - if (!file_exists($path)) return array('properties' => array()); - - // opening up the file, and creating a shared lock - $handle = fopen($path,'r'); - flock($handle,LOCK_SH); - $data = ''; - - // Reading data until the eof - while(!feof($handle)) { - $data.=fread($handle,8192); - } - - // We're all good - fclose($handle); - - // Unserializing and checking if the resource file contains data for this file - $data = unserialize($data); - if (!isset($data[$this->getName()])) { - return array('properties' => array()); - } - - $data = $data[$this->getName()]; - if (!isset($data['properties'])) $data['properties'] = array(); - return $data; - - } - - /** - * Updates the resource information - * - * @param array $newData - * @return void - */ - protected function putResourceData(array $newData) { - - $path = $this->getResourceInfoPath(); - - // opening up the file, and creating a shared lock - $handle = fopen($path,'a+'); - flock($handle,LOCK_EX); - $data = ''; - - rewind($handle); - - // Reading data until the eof - while(!feof($handle)) { - $data.=fread($handle,8192); - } - - // Unserializing and checking if the resource file contains data for this file - $data = unserialize($data); - $data[$this->getName()] = $newData; - ftruncate($handle,0); - rewind($handle); - - fwrite($handle,serialize($data)); - fclose($handle); - - } - - /** - * Renames the node - * - * @param string $name The new name - * @return void - */ - public function setName($name) { - - list($parentPath, ) = DAV\URLUtil::splitPath($this->path); - list(, $newName) = DAV\URLUtil::splitPath($name); - $newPath = $parentPath . '/' . $newName; - - // We're deleting the existing resourcedata, and recreating it - // for the new path. - $resourceData = $this->getResourceData(); - $this->deleteResourceData(); - - rename($this->path,$newPath); - $this->path = $newPath; - $this->putResourceData($resourceData); - - - } - - /** - * @return bool - */ - public function deleteResourceData() { - - // When we're deleting this node, we also need to delete any resource information - $path = $this->getResourceInfoPath(); - if (!file_exists($path)) return true; - - // opening up the file, and creating a shared lock - $handle = fopen($path,'a+'); - flock($handle,LOCK_EX); - $data = ''; - - rewind($handle); - - // Reading data until the eof - while(!feof($handle)) { - $data.=fread($handle,8192); - } - - // Unserializing and checking if the resource file contains data for this file - $data = unserialize($data); - if (isset($data[$this->getName()])) unset($data[$this->getName()]); - ftruncate($handle,0); - rewind($handle); - fwrite($handle,serialize($data)); - fclose($handle); - - return true; - } - - public function delete() { - - return $this->deleteResourceData(); - - } - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAV/File.php b/vendor/sabre/dav/lib/Sabre/DAV/File.php deleted file mode 100644 index af8ce735f..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/File.php +++ /dev/null @@ -1,85 +0,0 @@ - array( - * '{DAV:}displayname' => null, - * ), - * 424 => array( - * '{DAV:}owner' => null, - * ) - * ) - * - * In this example it was forbidden to update {DAV:}displayname. - * (403 Forbidden), which in turn also caused {DAV:}owner to fail - * (424 Failed Dependency) because the request needs to be atomic. - * - * @param array $mutations - * @return bool|array - */ - function updateProperties($mutations); - - /** - * Returns a list of properties for this nodes. - * - * The properties list is a list of propertynames the client requested, - * encoded in clark-notation {xmlnamespace}tagname - * - * If the array is empty, it means 'all properties' were requested. - * - * Note that it's fine to liberally give properties back, instead of - * conforming to the list of requested properties. - * The Server class will filter out the extra. - * - * @param array $properties - * @return void - */ - function getProperties($properties); - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAV/IQuota.php b/vendor/sabre/dav/lib/Sabre/DAV/IQuota.php deleted file mode 100644 index 988df3d06..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/IQuota.php +++ /dev/null @@ -1,27 +0,0 @@ -dataDir = $dataDir; - - } - - protected function getFileNameForUri($uri) { - - return $this->dataDir . '/sabredav_' . md5($uri) . '.locks'; - - } - - - /** - * Returns a list of Sabre\DAV\Locks\LockInfo objects - * - * This method should return all the locks for a particular uri, including - * locks that might be set on a parent uri. - * - * If returnChildLocks is set to true, this method should also look for - * any locks in the subtree of the uri for locks. - * - * @param string $uri - * @param bool $returnChildLocks - * @return array - */ - public function getLocks($uri, $returnChildLocks) { - - $lockList = array(); - $currentPath = ''; - - foreach(explode('/',$uri) as $uriPart) { - - // weird algorithm that can probably be improved, but we're traversing the path top down - if ($currentPath) $currentPath.='/'; - $currentPath.=$uriPart; - - $uriLocks = $this->getData($currentPath); - - foreach($uriLocks as $uriLock) { - - // Unless we're on the leaf of the uri-tree we should ignore locks with depth 0 - if($uri==$currentPath || $uriLock->depth!=0) { - $uriLock->uri = $currentPath; - $lockList[] = $uriLock; - } - - } - - } - - // Checking if we can remove any of these locks - foreach($lockList as $k=>$lock) { - if (time() > $lock->timeout + $lock->created) unset($lockList[$k]); - } - return $lockList; - - } - - /** - * Locks a uri - * - * @param string $uri - * @param LockInfo $lockInfo - * @return bool - */ - public function lock($uri, LockInfo $lockInfo) { - - // We're making the lock timeout 30 minutes - $lockInfo->timeout = 1800; - $lockInfo->created = time(); - - $locks = $this->getLocks($uri,false); - foreach($locks as $k=>$lock) { - if ($lock->token == $lockInfo->token) unset($locks[$k]); - } - $locks[] = $lockInfo; - $this->putData($uri,$locks); - return true; - - } - - /** - * Removes a lock from a uri - * - * @param string $uri - * @param LockInfo $lockInfo - * @return bool - */ - public function unlock($uri, LockInfo $lockInfo) { - - $locks = $this->getLocks($uri,false); - foreach($locks as $k=>$lock) { - - if ($lock->token == $lockInfo->token) { - - unset($locks[$k]); - $this->putData($uri,$locks); - return true; - - } - } - return false; - - } - - /** - * Returns the stored data for a uri - * - * @param string $uri - * @return array - */ - protected function getData($uri) { - - $path = $this->getFilenameForUri($uri); - if (!file_exists($path)) return array(); - - // opening up the file, and creating a shared lock - $handle = fopen($path,'r'); - flock($handle,LOCK_SH); - $data = ''; - - // Reading data until the eof - while(!feof($handle)) { - $data.=fread($handle,8192); - } - - // We're all good - fclose($handle); - - // Unserializing and checking if the resource file contains data for this file - $data = unserialize($data); - if (!$data) return array(); - return $data; - - } - - /** - * Updates the lock information - * - * @param string $uri - * @param array $newData - * @return void - */ - protected function putData($uri,array $newData) { - - $path = $this->getFileNameForUri($uri); - - // opening up the file, and creating a shared lock - $handle = fopen($path,'a+'); - flock($handle,LOCK_EX); - ftruncate($handle,0); - rewind($handle); - - fwrite($handle,serialize($newData)); - fclose($handle); - - } - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/File.php b/vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/File.php deleted file mode 100644 index 9ac7e06b2..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/File.php +++ /dev/null @@ -1,183 +0,0 @@ -locksFile = $locksFile; - - } - - /** - * Returns a list of Sabre\DAV\Locks\LockInfo objects - * - * This method should return all the locks for a particular uri, including - * locks that might be set on a parent uri. - * - * If returnChildLocks is set to true, this method should also look for - * any locks in the subtree of the uri for locks. - * - * @param string $uri - * @param bool $returnChildLocks - * @return array - */ - public function getLocks($uri, $returnChildLocks) { - - $newLocks = array(); - - $locks = $this->getData(); - - foreach($locks as $lock) { - - if ($lock->uri === $uri || - //deep locks on parents - ($lock->depth!=0 && strpos($uri, $lock->uri . '/')===0) || - - // locks on children - ($returnChildLocks && (strpos($lock->uri, $uri . '/')===0)) ) { - - $newLocks[] = $lock; - - } - - } - - // Checking if we can remove any of these locks - foreach($newLocks as $k=>$lock) { - if (time() > $lock->timeout + $lock->created) unset($newLocks[$k]); - } - return $newLocks; - - } - - /** - * Locks a uri - * - * @param string $uri - * @param LockInfo $lockInfo - * @return bool - */ - public function lock($uri, LockInfo $lockInfo) { - - // We're making the lock timeout 30 minutes - $lockInfo->timeout = 1800; - $lockInfo->created = time(); - $lockInfo->uri = $uri; - - $locks = $this->getData(); - - foreach($locks as $k=>$lock) { - if ( - ($lock->token == $lockInfo->token) || - (time() > $lock->timeout + $lock->created) - ) { - unset($locks[$k]); - } - } - $locks[] = $lockInfo; - $this->putData($locks); - return true; - - } - - /** - * Removes a lock from a uri - * - * @param string $uri - * @param LockInfo $lockInfo - * @return bool - */ - public function unlock($uri, LockInfo $lockInfo) { - - $locks = $this->getData(); - foreach($locks as $k=>$lock) { - - if ($lock->token == $lockInfo->token) { - - unset($locks[$k]); - $this->putData($locks); - return true; - - } - } - return false; - - } - - /** - * Loads the lockdata from the filesystem. - * - * @return array - */ - protected function getData() { - - if (!file_exists($this->locksFile)) return array(); - - // opening up the file, and creating a shared lock - $handle = fopen($this->locksFile,'r'); - flock($handle,LOCK_SH); - - // Reading data until the eof - $data = stream_get_contents($handle); - - // We're all good - fclose($handle); - - // Unserializing and checking if the resource file contains data for this file - $data = unserialize($data); - if (!$data) return array(); - return $data; - - } - - /** - * Saves the lockdata - * - * @param array $newData - * @return void - */ - protected function putData(array $newData) { - - // opening up the file, and creating an exclusive lock - $handle = fopen($this->locksFile,'a+'); - flock($handle,LOCK_EX); - - // We can only truncate and rewind once the lock is acquired. - ftruncate($handle,0); - rewind($handle); - - fwrite($handle,serialize($newData)); - fclose($handle); - - } - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/PDO.php b/vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/PDO.php deleted file mode 100644 index ebaeef860..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/PDO.php +++ /dev/null @@ -1,167 +0,0 @@ -pdo = $pdo; - $this->tableName = $tableName; - - } - - /** - * Returns a list of Sabre\DAV\Locks\LockInfo objects - * - * This method should return all the locks for a particular uri, including - * locks that might be set on a parent uri. - * - * If returnChildLocks is set to true, this method should also look for - * any locks in the subtree of the uri for locks. - * - * @param string $uri - * @param bool $returnChildLocks - * @return array - */ - public function getLocks($uri, $returnChildLocks) { - - // NOTE: the following 10 lines or so could be easily replaced by - // pure sql. MySQL's non-standard string concatenation prevents us - // from doing this though. - $query = 'SELECT owner, token, timeout, created, scope, depth, uri FROM '.$this->tableName.' WHERE ((created + timeout) > CAST(? AS UNSIGNED INTEGER)) AND ((uri = ?)'; - $params = array(time(),$uri); - - // We need to check locks for every part in the uri. - $uriParts = explode('/',$uri); - - // We already covered the last part of the uri - array_pop($uriParts); - - $currentPath=''; - - foreach($uriParts as $part) { - - if ($currentPath) $currentPath.='/'; - $currentPath.=$part; - - $query.=' OR (depth!=0 AND uri = ?)'; - $params[] = $currentPath; - - } - - if ($returnChildLocks) { - - $query.=' OR (uri LIKE ?)'; - $params[] = $uri . '/%'; - - } - $query.=')'; - - $stmt = $this->pdo->prepare($query); - $stmt->execute($params); - $result = $stmt->fetchAll(); - - $lockList = array(); - foreach($result as $row) { - - $lockInfo = new LockInfo(); - $lockInfo->owner = $row['owner']; - $lockInfo->token = $row['token']; - $lockInfo->timeout = $row['timeout']; - $lockInfo->created = $row['created']; - $lockInfo->scope = $row['scope']; - $lockInfo->depth = $row['depth']; - $lockInfo->uri = $row['uri']; - $lockList[] = $lockInfo; - - } - - return $lockList; - - } - - /** - * Locks a uri - * - * @param string $uri - * @param LockInfo $lockInfo - * @return bool - */ - public function lock($uri, LockInfo $lockInfo) { - - // We're making the lock timeout 30 minutes - $lockInfo->timeout = 30*60; - $lockInfo->created = time(); - $lockInfo->uri = $uri; - - $locks = $this->getLocks($uri,false); - $exists = false; - foreach($locks as $lock) { - if ($lock->token == $lockInfo->token) $exists = true; - } - - if ($exists) { - $stmt = $this->pdo->prepare('UPDATE '.$this->tableName.' SET owner = ?, timeout = ?, scope = ?, depth = ?, uri = ?, created = ? WHERE token = ?'); - $stmt->execute(array($lockInfo->owner,$lockInfo->timeout,$lockInfo->scope,$lockInfo->depth,$uri,$lockInfo->created,$lockInfo->token)); - } else { - $stmt = $this->pdo->prepare('INSERT INTO '.$this->tableName.' (owner,timeout,scope,depth,uri,created,token) VALUES (?,?,?,?,?,?,?)'); - $stmt->execute(array($lockInfo->owner,$lockInfo->timeout,$lockInfo->scope,$lockInfo->depth,$uri,$lockInfo->created,$lockInfo->token)); - } - - return true; - - } - - - - /** - * Removes a lock from a uri - * - * @param string $uri - * @param LockInfo $lockInfo - * @return bool - */ - public function unlock($uri, LockInfo $lockInfo) { - - $stmt = $this->pdo->prepare('DELETE FROM '.$this->tableName.' WHERE uri = ? AND token = ?'); - $stmt->execute(array($uri,$lockInfo->token)); - - return $stmt->rowCount()===1; - - } - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Locks/LockInfo.php b/vendor/sabre/dav/lib/Sabre/DAV/Locks/LockInfo.php deleted file mode 100644 index 74bdb0f9c..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Locks/LockInfo.php +++ /dev/null @@ -1,81 +0,0 @@ -addPlugin($lockPlugin); - * - * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/). - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class Plugin extends DAV\ServerPlugin { - - /** - * locksBackend - * - * @var Backend\Backend\Interface - */ - protected $locksBackend; - - /** - * server - * - * @var Sabre\DAV\Server - */ - protected $server; - - /** - * __construct - * - * @param Backend\BackendInterface $locksBackend - */ - public function __construct(Backend\BackendInterface $locksBackend = null) { - - $this->locksBackend = $locksBackend; - - } - - /** - * Initializes the plugin - * - * This method is automatically called by the Server class after addPlugin. - * - * @param DAV\Server $server - * @return void - */ - public function initialize(DAV\Server $server) { - - $this->server = $server; - $server->subscribeEvent('unknownMethod',array($this,'unknownMethod')); - $server->subscribeEvent('beforeMethod',array($this,'beforeMethod'),50); - $server->subscribeEvent('afterGetProperties',array($this,'afterGetProperties')); - - } - - /** - * Returns a plugin name. - * - * Using this name other plugins will be able to access other plugins - * using Sabre\DAV\Server::getPlugin - * - * @return string - */ - public function getPluginName() { - - return 'locks'; - - } - - /** - * This method is called by the Server if the user used an HTTP method - * the server didn't recognize. - * - * This plugin intercepts the LOCK and UNLOCK methods. - * - * @param string $method - * @param string $uri - * @return bool - */ - public function unknownMethod($method, $uri) { - - switch($method) { - - case 'LOCK' : $this->httpLock($uri); return false; - case 'UNLOCK' : $this->httpUnlock($uri); return false; - - } - - } - - /** - * This method is called after most properties have been found - * it allows us to add in any Lock-related properties - * - * @param string $path - * @param array $newProperties - * @return bool - */ - public function afterGetProperties($path, &$newProperties) { - - foreach($newProperties[404] as $propName=>$discard) { - - switch($propName) { - - case '{DAV:}supportedlock' : - $val = false; - if ($this->locksBackend) $val = true; - $newProperties[200][$propName] = new DAV\Property\SupportedLock($val); - unset($newProperties[404][$propName]); - break; - - case '{DAV:}lockdiscovery' : - $newProperties[200][$propName] = new DAV\Property\LockDiscovery($this->getLocks($path)); - unset($newProperties[404][$propName]); - break; - - } - - - } - return true; - - } - - - /** - * This method is called before the logic for any HTTP method is - * handled. - * - * This plugin uses that feature to intercept access to locked resources. - * - * @param string $method - * @param string $uri - * @return bool - */ - public function beforeMethod($method, $uri) { - - switch($method) { - - case 'DELETE' : - $lastLock = null; - if (!$this->validateLock($uri,$lastLock, true)) - throw new DAV\Exception\Locked($lastLock); - break; - case 'MKCOL' : - case 'PROPPATCH' : - case 'PUT' : - case 'PATCH' : - $lastLock = null; - if (!$this->validateLock($uri,$lastLock)) - throw new DAV\Exception\Locked($lastLock); - break; - case 'MOVE' : - $lastLock = null; - if (!$this->validateLock(array( - $uri, - $this->server->calculateUri($this->server->httpRequest->getHeader('Destination')), - ),$lastLock, true)) - throw new DAV\Exception\Locked($lastLock); - break; - case 'COPY' : - $lastLock = null; - if (!$this->validateLock( - $this->server->calculateUri($this->server->httpRequest->getHeader('Destination')), - $lastLock, true)) - throw new DAV\Exception\Locked($lastLock); - break; - } - - return true; - - } - - /** - * Use this method to tell the server this plugin defines additional - * HTTP methods. - * - * This method is passed a uri. It should only return HTTP methods that are - * available for the specified uri. - * - * @param string $uri - * @return array - */ - public function getHTTPMethods($uri) { - - if ($this->locksBackend) - return array('LOCK','UNLOCK'); - - return array(); - - } - - /** - * Returns a list of features for the HTTP OPTIONS Dav: header. - * - * In this case this is only the number 2. The 2 in the Dav: header - * indicates the server supports locks. - * - * @return array - */ - public function getFeatures() { - - return array(2); - - } - - /** - * Returns all lock information on a particular uri - * - * This function should return an array with Sabre\DAV\Locks\LockInfo objects. If there are no locks on a file, return an empty array. - * - * Additionally there is also the possibility of locks on parent nodes, so we'll need to traverse every part of the tree - * If the $returnChildLocks argument is set to true, we'll also traverse all the children of the object - * for any possible locks and return those as well. - * - * @param string $uri - * @param bool $returnChildLocks - * @return array - */ - public function getLocks($uri, $returnChildLocks = false) { - - $lockList = array(); - - if ($this->locksBackend) - $lockList = array_merge($lockList,$this->locksBackend->getLocks($uri, $returnChildLocks)); - - return $lockList; - - } - - /** - * Locks an uri - * - * The WebDAV lock request can be operated to either create a new lock on a file, or to refresh an existing lock - * If a new lock is created, a full XML body should be supplied, containing information about the lock such as the type - * of lock (shared or exclusive) and the owner of the lock - * - * If a lock is to be refreshed, no body should be supplied and there should be a valid If header containing the lock - * - * Additionally, a lock can be requested for a non-existent file. In these case we're obligated to create an empty file as per RFC4918:S7.3 - * - * @param string $uri - * @return void - */ - protected function httpLock($uri) { - - $lastLock = null; - if (!$this->validateLock($uri,$lastLock)) { - - // If the existing lock was an exclusive lock, we need to fail - if (!$lastLock || $lastLock->scope == LockInfo::EXCLUSIVE) { - //var_dump($lastLock); - throw new DAV\Exception\ConflictingLock($lastLock); - } - - } - - if ($body = $this->server->httpRequest->getBody(true)) { - // This is a new lock request - $lockInfo = $this->parseLockRequest($body); - $lockInfo->depth = $this->server->getHTTPDepth(); - $lockInfo->uri = $uri; - if($lastLock && $lockInfo->scope != LockInfo::SHARED) throw new DAV\Exception\ConflictingLock($lastLock); - - } elseif ($lastLock) { - - // This must have been a lock refresh - $lockInfo = $lastLock; - - // The resource could have been locked through another uri. - if ($uri!=$lockInfo->uri) $uri = $lockInfo->uri; - - } else { - - // There was neither a lock refresh nor a new lock request - throw new DAV\Exception\BadRequest('An xml body is required for lock requests'); - - } - - if ($timeout = $this->getTimeoutHeader()) $lockInfo->timeout = $timeout; - - $newFile = false; - - // If we got this far.. we should go check if this node actually exists. If this is not the case, we need to create it first - try { - $this->server->tree->getNodeForPath($uri); - - // We need to call the beforeWriteContent event for RFC3744 - // Edit: looks like this is not used, and causing problems now. - // - // See Issue 222 - // $this->server->broadcastEvent('beforeWriteContent',array($uri)); - - } catch (DAV\Exception\NotFound $e) { - - // It didn't, lets create it - $this->server->createFile($uri,fopen('php://memory','r')); - $newFile = true; - - } - - $this->lockNode($uri,$lockInfo); - - $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); - $this->server->httpResponse->setHeader('Lock-Token','token . '>'); - $this->server->httpResponse->sendStatus($newFile?201:200); - $this->server->httpResponse->sendBody($this->generateLockResponse($lockInfo)); - - } - - /** - * Unlocks a uri - * - * This WebDAV method allows you to remove a lock from a node. The client should provide a valid locktoken through the Lock-token http header - * The server should return 204 (No content) on success - * - * @param string $uri - * @return void - */ - protected function httpUnlock($uri) { - - $lockToken = $this->server->httpRequest->getHeader('Lock-Token'); - - // If the locktoken header is not supplied, we need to throw a bad request exception - if (!$lockToken) throw new DAV\Exception\BadRequest('No lock token was supplied'); - - $locks = $this->getLocks($uri); - - // Windows sometimes forgets to include < and > in the Lock-Token - // header - if ($lockToken[0]!=='<') $lockToken = '<' . $lockToken . '>'; - - foreach($locks as $lock) { - - if ('token . '>' == $lockToken) { - - $this->unlockNode($uri,$lock); - $this->server->httpResponse->setHeader('Content-Length','0'); - $this->server->httpResponse->sendStatus(204); - return; - - } - - } - - // If we got here, it means the locktoken was invalid - throw new DAV\Exception\LockTokenMatchesRequestUri(); - - } - - /** - * Locks a uri - * - * All the locking information is supplied in the lockInfo object. The object has a suggested timeout, but this can be safely ignored - * It is important that if the existing timeout is ignored, the property is overwritten, as this needs to be sent back to the client - * - * @param string $uri - * @param LockInfo $lockInfo - * @return bool - */ - public function lockNode($uri,LockInfo $lockInfo) { - - if (!$this->server->broadcastEvent('beforeLock',array($uri,$lockInfo))) return; - - if ($this->locksBackend) return $this->locksBackend->lock($uri,$lockInfo); - throw new DAV\Exception\MethodNotAllowed('Locking support is not enabled for this resource. No Locking backend was found so if you didn\'t expect this error, please check your configuration.'); - - } - - /** - * Unlocks a uri - * - * This method removes a lock from a uri. It is assumed all the supplied information is correct and verified - * - * @param string $uri - * @param LockInfo $lockInfo - * @return bool - */ - public function unlockNode($uri, LockInfo $lockInfo) { - - if (!$this->server->broadcastEvent('beforeUnlock',array($uri,$lockInfo))) return; - if ($this->locksBackend) return $this->locksBackend->unlock($uri,$lockInfo); - - } - - - /** - * Returns the contents of the HTTP Timeout header. - * - * The method formats the header into an integer. - * - * @return int - */ - public function getTimeoutHeader() { - - $header = $this->server->httpRequest->getHeader('Timeout'); - - if ($header) { - - if (stripos($header,'second-')===0) $header = (int)(substr($header,7)); - else if (strtolower($header)=='infinite') $header = LockInfo::TIMEOUT_INFINITE; - else throw new DAV\Exception\BadRequest('Invalid HTTP timeout header'); - - } else { - - $header = 0; - - } - - return $header; - - } - - /** - * Generates the response for successful LOCK requests - * - * @param LockInfo $lockInfo - * @return string - */ - protected function generateLockResponse(LockInfo $lockInfo) { - - $dom = new \DOMDocument('1.0','utf-8'); - $dom->formatOutput = true; - - $prop = $dom->createElementNS('DAV:','d:prop'); - $dom->appendChild($prop); - - $lockDiscovery = $dom->createElementNS('DAV:','d:lockdiscovery'); - $prop->appendChild($lockDiscovery); - - $lockObj = new DAV\Property\LockDiscovery(array($lockInfo),true); - $lockObj->serialize($this->server,$lockDiscovery); - - return $dom->saveXML(); - - } - - /** - * validateLock should be called when a write operation is about to happen - * It will check if the requested url is locked, and see if the correct lock tokens are passed - * - * @param mixed $urls List of relevant urls. Can be an array, a string or nothing at all for the current request uri - * @param mixed $lastLock This variable will be populated with the last checked lock object (Sabre\DAV\Locks\LockInfo) - * @param bool $checkChildLocks If set to true, this function will also look for any locks set on child resources of the supplied urls. This is needed for for example deletion of entire trees. - * @return bool - */ - protected function validateLock($urls = null,&$lastLock = null, $checkChildLocks = false) { - - if (is_null($urls)) { - $urls = array($this->server->getRequestUri()); - } elseif (is_string($urls)) { - $urls = array($urls); - } elseif (!is_array($urls)) { - throw new DAV\Exception('The urls parameter should either be null, a string or an array'); - } - - $conditions = $this->getIfConditions(); - - // We're going to loop through the urls and make sure all lock conditions are satisfied - foreach($urls as $url) { - - $locks = $this->getLocks($url, $checkChildLocks); - - // If there were no conditions, but there were locks, we fail - if (!$conditions && $locks) { - reset($locks); - $lastLock = current($locks); - return false; - } - - // If there were no locks or conditions, we go to the next url - if (!$locks && !$conditions) continue; - - foreach($conditions as $condition) { - - if (!$condition['uri']) { - $conditionUri = $this->server->getRequestUri(); - } else { - $conditionUri = $this->server->calculateUri($condition['uri']); - } - - // If the condition has a url, and it isn't part of the affected url at all, check the next condition - if ($conditionUri && strpos($url,$conditionUri)!==0) continue; - - // The tokens array contians arrays with 2 elements. 0=true/false for normal/not condition, 1=locktoken - // At least 1 condition has to be satisfied - foreach($condition['tokens'] as $conditionToken) { - - $etagValid = true; - $lockValid = true; - - // key 2 can contain an etag - if ($conditionToken[2]) { - - $uri = $conditionUri?$conditionUri:$this->server->getRequestUri(); - $node = $this->server->tree->getNodeForPath($uri); - $etagValid = $node instanceof DAV\IFile && $node->getETag()==$conditionToken[2]; - - } - - // key 1 can contain a lock token - if ($conditionToken[1]) { - - $lockValid = false; - // Match all the locks - foreach($locks as $lockIndex=>$lock) { - - $lockToken = 'opaquelocktoken:' . $lock->token; - - // Checking NOT - if (!$conditionToken[0] && $lockToken != $conditionToken[1]) { - - // Condition valid, onto the next - $lockValid = true; - break; - } - if ($conditionToken[0] && $lockToken == $conditionToken[1]) { - - $lastLock = $lock; - // Condition valid and lock matched - unset($locks[$lockIndex]); - $lockValid = true; - break; - - } - - } - - } - - // If, after checking both etags and locks they are stil valid, - // we can continue with the next condition. - if ($etagValid && $lockValid) continue 2; - } - // No conditions matched, so we fail - throw new DAV\Exception\PreconditionFailed('The tokens provided in the if header did not match','If'); - } - - // Conditions were met, we'll also need to check if all the locks are gone - if (count($locks)) { - - reset($locks); - - // There's still locks, we fail - $lastLock = current($locks); - return false; - - } - - - } - - // We got here, this means every condition was satisfied - return true; - - } - - /** - * This method is created to extract information from the WebDAV HTTP 'If:' header - * - * The If header can be quite complex, and has a bunch of features. We're using a regex to extract all relevant information - * The function will return an array, containing structs with the following keys - * - * * uri - the uri the condition applies to. If this is returned as an - * empty string, this implies it's referring to the request url. - * * tokens - The lock token. another 2 dimensional array containing 2 elements (0 = true/false.. If this is a negative condition its set to false, 1 = the actual token) - * * etag - an etag, if supplied - * - * @return array - */ - public function getIfConditions() { - - $header = $this->server->httpRequest->getHeader('If'); - if (!$header) return array(); - - $matches = array(); - - $regex = '/(?:\<(?P.*?)\>\s)?\((?PNot\s)?(?:\<(?P[^\>]*)\>)?(?:\s?)(?:\[(?P[^\]]*)\])?\)/im'; - preg_match_all($regex,$header,$matches,PREG_SET_ORDER); - - $conditions = array(); - - foreach($matches as $match) { - - $condition = array( - 'uri' => $match['uri'], - 'tokens' => array( - array($match['not']?0:1,$match['token'],isset($match['etag'])?$match['etag']:'') - ), - ); - - if (!$condition['uri'] && count($conditions)) $conditions[count($conditions)-1]['tokens'][] = array( - $match['not']?0:1, - $match['token'], - isset($match['etag'])?$match['etag']:'' - ); - else { - $conditions[] = $condition; - } - - } - - return $conditions; - - } - - /** - * Parses a webdav lock xml body, and returns a new Sabre\DAV\Locks\LockInfo object - * - * @param string $body - * @return DAV\Locks\LockInfo - */ - protected function parseLockRequest($body) { - - // Fixes an XXE vulnerability on PHP versions older than 5.3.23 or - // 5.4.13. - $previous = libxml_disable_entity_loader(true); - - - $xml = simplexml_load_string( - DAV\XMLUtil::convertDAVNamespace($body), - null, - LIBXML_NOWARNING); - libxml_disable_entity_loader($previous); - - $xml->registerXPathNamespace('d','urn:DAV'); - $lockInfo = new LockInfo(); - - $children = $xml->children("urn:DAV"); - $lockInfo->owner = (string)$children->owner; - - $lockInfo->token = DAV\UUIDUtil::getUUID(); - $lockInfo->scope = count($xml->xpath('d:lockscope/d:exclusive'))>0 ? LockInfo::EXCLUSIVE : LockInfo::SHARED; - - return $lockInfo; - - } - - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Mount/Plugin.php b/vendor/sabre/dav/lib/Sabre/DAV/Mount/Plugin.php deleted file mode 100644 index 8376b03b0..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Mount/Plugin.php +++ /dev/null @@ -1,83 +0,0 @@ -server = $server; - $this->server->subscribeEvent('beforeMethod',array($this,'beforeMethod'), 90); - - } - - /** - * 'beforeMethod' event handles. This event handles intercepts GET requests ending - * with ?mount - * - * @param string $method - * @param string $uri - * @return bool - */ - public function beforeMethod($method, $uri) { - - if ($method!='GET') return; - if ($this->server->httpRequest->getQueryString()!='mount') return; - - $currentUri = $this->server->httpRequest->getAbsoluteUri(); - - // Stripping off everything after the ? - list($currentUri) = explode('?',$currentUri); - - $this->davMount($currentUri); - - // Returning false to break the event chain - return false; - - } - - /** - * Generates the davmount response - * - * @param string $uri absolute uri - * @return void - */ - public function davMount($uri) { - - $this->server->httpResponse->sendStatus(200); - $this->server->httpResponse->setHeader('Content-Type','application/davmount+xml'); - ob_start(); - echo '', "\n"; - echo "\n"; - echo " ", htmlspecialchars($uri, ENT_NOQUOTES, 'UTF-8'), "\n"; - echo ""; - $this->server->httpResponse->sendBody(ob_get_clean()); - - } - - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Node.php b/vendor/sabre/dav/lib/Sabre/DAV/Node.php deleted file mode 100644 index 44e47be68..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Node.php +++ /dev/null @@ -1,55 +0,0 @@ -rootNode = $rootNode; - - } - - /** - * Returns the INode object for the requested path - * - * @param string $path - * @return INode - */ - public function getNodeForPath($path) { - - $path = trim($path,'/'); - if (isset($this->cache[$path])) return $this->cache[$path]; - - // Is it the root node? - if (!strlen($path)) { - return $this->rootNode; - } - - // Attempting to fetch its parent - list($parentName, $baseName) = URLUtil::splitPath($path); - - // If there was no parent, we must simply ask it from the root node. - if ($parentName==="") { - $node = $this->rootNode->getChild($baseName); - } else { - // Otherwise, we recursively grab the parent and ask him/her. - $parent = $this->getNodeForPath($parentName); - - if (!($parent instanceof ICollection)) - throw new Exception\NotFound('Could not find node at path: ' . $path); - - $node = $parent->getChild($baseName); - - } - - $this->cache[$path] = $node; - return $node; - - } - - /** - * This function allows you to check if a node exists. - * - * @param string $path - * @return bool - */ - public function nodeExists($path) { - - try { - - // The root always exists - if ($path==='') return true; - - list($parent, $base) = URLUtil::splitPath($path); - - $parentNode = $this->getNodeForPath($parent); - if (!$parentNode instanceof ICollection) return false; - return $parentNode->childExists($base); - - } catch (Exception\NotFound $e) { - - return false; - - } - - } - - /** - * Returns a list of childnodes for a given path. - * - * @param string $path - * @return array - */ - public function getChildren($path) { - - $node = $this->getNodeForPath($path); - $children = $node->getChildren(); - foreach($children as $child) { - - $this->cache[trim($path,'/') . '/' . $child->getName()] = $child; - - } - return $children; - - } - - /** - * This method is called with every tree update - * - * Examples of tree updates are: - * * node deletions - * * node creations - * * copy - * * move - * * renaming nodes - * - * If Tree classes implement a form of caching, this will allow - * them to make sure caches will be expired. - * - * If a path is passed, it is assumed that the entire subtree is dirty - * - * @param string $path - * @return void - */ - public function markDirty($path) { - - // We don't care enough about sub-paths - // flushing the entire cache - $path = trim($path,'/'); - foreach($this->cache as $nodePath=>$node) { - if ($nodePath == $path || strpos($nodePath,$path.'/')===0) - unset($this->cache[$nodePath]); - - } - - } - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAV/PartialUpdate/IFile.php b/vendor/sabre/dav/lib/Sabre/DAV/PartialUpdate/IFile.php deleted file mode 100644 index 9cfb47377..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/PartialUpdate/IFile.php +++ /dev/null @@ -1,39 +0,0 @@ -addPlugin($patchPlugin); - * - * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/). - * @author Jean-Tiare LE BIGOT (http://www.jtlebi.fr/) - * @license http://sabre.io/license/ Modified BSD License - */ -class Plugin extends DAV\ServerPlugin { - - const RANGE_APPEND = 1; - const RANGE_START = 2; - const RANGE_END = 3; - - /** - * Reference to server - * - * @var Sabre\DAV\Server - */ - protected $server; - - /** - * Initializes the plugin - * - * This method is automatically called by the Server class after addPlugin. - * - * @param DAV\Server $server - * @return void - */ - public function initialize(DAV\Server $server) { - - $this->server = $server; - $server->subscribeEvent('unknownMethod',array($this,'unknownMethod')); - - } - - /** - * Returns a plugin name. - * - * Using this name other plugins will be able to access other plugins - * using DAV\Server::getPlugin - * - * @return string - */ - public function getPluginName() { - - return 'partialupdate'; - - } - - /** - * This method is called by the Server if the user used an HTTP method - * the server didn't recognize. - * - * This plugin intercepts the PATCH methods. - * - * @param string $method - * @param string $uri - * @return bool|null - */ - public function unknownMethod($method, $uri) { - - switch($method) { - - case 'PATCH': - return $this->httpPatch($uri); - - } - - } - - /** - * Use this method to tell the server this plugin defines additional - * HTTP methods. - * - * This method is passed a uri. It should only return HTTP methods that are - * available for the specified uri. - * - * We claim to support PATCH method (partial update) if and only if - * - the node exist - * - the node implements our partial update interface - * - * @param string $uri - * @return array - */ - public function getHTTPMethods($uri) { - - $tree = $this->server->tree; - if ($tree->nodeExists($uri)) { - $node = $tree->getNodeForPath($uri); - if ($node instanceof IFile || $node instanceof IPatchSupport) { - return array('PATCH'); - } - } - return array(); - - } - - /** - * Returns a list of features for the HTTP OPTIONS Dav: header. - * - * @return array - */ - public function getFeatures() { - - return array('sabredav-partialupdate'); - - } - - /** - * Patch an uri - * - * The WebDAV patch request can be used to modify only a part of an - * existing resource. If the resource does not exist yet and the first - * offset is not 0, the request fails - * - * @param string $uri - * @return void - */ - protected function httpPatch($uri) { - - // Get the node. Will throw a 404 if not found - $node = $this->server->tree->getNodeForPath($uri); - if (!$node instanceof IFile && !$node instanceof IPatchSupport) { - throw new DAV\Exception\MethodNotAllowed('The target resource does not support the PATCH method.'); - } - - $range = $this->getHTTPUpdateRange(); - - if (!$range) { - throw new DAV\Exception\BadRequest('No valid "X-Update-Range" found in the headers'); - } - - $contentType = strtolower( - $this->server->httpRequest->getHeader('Content-Type') - ); - - if ($contentType != 'application/x-sabredav-partialupdate') { - throw new DAV\Exception\UnsupportedMediaType('Unknown Content-Type header "' . $contentType . '"'); - } - - $len = $this->server->httpRequest->getHeader('Content-Length'); - if (!$len) throw new DAV\Exception\LengthRequired('A Content-Length header is required'); - - switch($range[0]) { - case self::RANGE_START : - // Calculate the end-range if it doesn't exist. - if (!$range[2]) { - $range[2] = $range[1] + $len - 1; - } else { - if ($range[2] < $range[1]) { - throw new DAV\Exception\RequestedRangeNotSatisfiable('The end offset (' . $range[2] . ') is lower than the start offset (' . $range[1] . ')'); - } - if($range[2] - $range[1] + 1 != $len) { - throw new DAV\Exception\RequestedRangeNotSatisfiable('Actual data length (' . $len . ') is not consistent with begin (' . $range[1] . ') and end (' . $range[2] . ') offsets'); - } - } - break; - } - // Checking If-None-Match and related headers. - if (!$this->server->checkPreconditions()) return; - - if (!$this->server->broadcastEvent('beforeWriteContent',array($uri, $node, null))) - return; - - $body = $this->server->httpRequest->getBody(); - - - if ($node instanceof IPatchSupport) { - $etag = $node->patch($body, $range[0], isset($range[1])?$range[1]:null); - } else { - // The old interface - switch($range[0]) { - case self::RANGE_APPEND : - throw new DAV\Exception\NotImplemented('This node does not support the append syntax. Please upgrade it to IPatchSupport'); - case self::RANGE_START : - $etag = $node->putRange($body, $range[1]); - break; - case self::RANGE_END : - throw new DAV\Exception\NotImplemented('This node does not support the end-range syntax. Please upgrade it to IPatchSupport'); - break; - } - } - - $this->server->broadcastEvent('afterWriteContent',array($uri, $node)); - - $this->server->httpResponse->setHeader('Content-Length','0'); - if ($etag) $this->server->httpResponse->setHeader('ETag',$etag); - $this->server->httpResponse->sendStatus(204); - - return false; - - } - - /** - * Returns the HTTP custom range update header - * - * This method returns null if there is no well-formed HTTP range request - * header. It returns array(1) if it was an append request, array(2, - * $start, $end) if it's a start and end range, lastly it's array(3, - * $endoffset) if the offset was negative, and should be calculated from - * the end of the file. - * - * Examples: - * - * null - invalid - * array(1) - append - * array(2,10,15) - update bytes 10, 11, 12, 13, 14, 15 - * array(2,10,null) - update bytes 10 until the end of the patch body - * array(3,-5) - update from 5 bytes from the end of the file. - * - * @return array|null - */ - public function getHTTPUpdateRange() { - - $range = $this->server->httpRequest->getHeader('X-Update-Range'); - if (is_null($range)) return null; - - // Matching "Range: bytes=1234-5678: both numbers are optional - - if (!preg_match('/^(append)|(?:bytes=([0-9]+)-([0-9]*))|(?:bytes=(-[0-9]+))$/i',$range,$matches)) return null; - - if ($matches[1]==='append') { - return array(self::RANGE_APPEND); - } elseif (strlen($matches[2])>0) { - return array(self::RANGE_START, $matches[2], $matches[3]?:null); - } elseif ($matches[4]) { - return array(self::RANGE_END, $matches[4]); - } else { - return null; - } - - } -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Property.php b/vendor/sabre/dav/lib/Sabre/DAV/Property.php deleted file mode 100644 index d0c265907..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Property.php +++ /dev/null @@ -1,31 +0,0 @@ -time = $time; - } elseif (is_int($time) || ctype_digit($time)) { - $this->time = new \DateTime('@' . $time); - } else { - $this->time = new \DateTime($time); - } - - // Setting timezone to UTC - $this->time->setTimezone(new \DateTimeZone('UTC')); - - } - - /** - * serialize - * - * @param DAV\Server $server - * @param \DOMElement $prop - * @return void - */ - public function serialize(DAV\Server $server, \DOMElement $prop) { - - $doc = $prop->ownerDocument; - //$prop->setAttribute('xmlns:b','urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/'); - //$prop->setAttribute('b:dt','dateTime.rfc1123'); - $prop->nodeValue = HTTP\Util::toHTTPDate($this->time); - - } - - /** - * getTime - * - * @return \DateTime - */ - public function getTime() { - - return $this->time; - - } - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Property/Href.php b/vendor/sabre/dav/lib/Sabre/DAV/Property/Href.php deleted file mode 100644 index f0c162706..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Property/Href.php +++ /dev/null @@ -1,99 +0,0 @@ -href = $href; - $this->autoPrefix = $autoPrefix; - - } - - /** - * Returns the uri - * - * @return string - */ - public function getHref() { - - return $this->href; - - } - - /** - * Serializes this property. - * - * It will additionally prepend the href property with the server's base uri. - * - * @param DAV\Server $server - * @param \DOMElement $dom - * @return void - */ - public function serialize(DAV\Server $server, \DOMElement $dom) { - - $prefix = $server->xmlNamespaces['DAV:']; - $elem = $dom->ownerDocument->createElement($prefix . ':href'); - - if ($this->autoPrefix) { - $value = $server->getBaseUri() . DAV\URLUtil::encodePath($this->href); - } else { - $value = $this->href; - } - $elem->appendChild($dom->ownerDocument->createTextNode($value)); - - $dom->appendChild($elem); - - } - - /** - * Unserializes this property from a DOM Element - * - * This method returns an instance of this class. - * It will only decode {DAV:}href values. For non-compatible elements null will be returned. - * - * @param \DOMElement $dom - * @return DAV\Property\Href - */ - static function unserialize(\DOMElement $dom) { - - if ($dom->firstChild && DAV\XMLUtil::toClarkNotation($dom->firstChild)==='{DAV:}href') { - return new self($dom->firstChild->textContent,false); - } - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Property/HrefList.php b/vendor/sabre/dav/lib/Sabre/DAV/Property/HrefList.php deleted file mode 100644 index a5bad4ace..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Property/HrefList.php +++ /dev/null @@ -1,105 +0,0 @@ -hrefs = $hrefs; - $this->autoPrefix = $autoPrefix; - - } - - /** - * Returns the uris - * - * @return array - */ - public function getHrefs() { - - return $this->hrefs; - - } - - /** - * Serializes this property. - * - * It will additionally prepend the href property with the server's base uri. - * - * @param DAV\Server $server - * @param \DOMElement $dom - * @return void - */ - public function serialize(DAV\Server $server,\DOMElement $dom) { - - $prefix = $server->xmlNamespaces['DAV:']; - - foreach($this->hrefs as $href) { - - $elem = $dom->ownerDocument->createElement($prefix . ':href'); - if ($this->autoPrefix) { - $value = $server->getBaseUri() . DAV\URLUtil::encodePath($href); - } else { - $value = $href; - } - $elem->appendChild($dom->ownerDocument->createTextNode($value)); - - $dom->appendChild($elem); - } - - } - - /** - * Unserializes this property from a DOM Element - * - * This method returns an instance of this class. - * It will only decode {DAV:}href values. - * - * @param \DOMElement $dom - * @return DAV\Property\HrefList - */ - static function unserialize(\DOMElement $dom) { - - $hrefs = array(); - foreach($dom->childNodes as $child) { - if (DAV\XMLUtil::toClarkNotation($child)==='{DAV:}href') { - $hrefs[] = $child->textContent; - } - } - return new self($hrefs, false); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Property/IHref.php b/vendor/sabre/dav/lib/Sabre/DAV/Property/IHref.php deleted file mode 100644 index 268ab8d51..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Property/IHref.php +++ /dev/null @@ -1,25 +0,0 @@ -locks = $locks; - $this->revealLockToken = $revealLockToken; - - } - - /** - * serialize - * - * @param DAV\Server $server - * @param \DOMElement $prop - * @return void - */ - public function serialize(DAV\Server $server, \DOMElement $prop) { - - $doc = $prop->ownerDocument; - - foreach($this->locks as $lock) { - - $activeLock = $doc->createElementNS('DAV:','d:activelock'); - $prop->appendChild($activeLock); - - $lockScope = $doc->createElementNS('DAV:','d:lockscope'); - $activeLock->appendChild($lockScope); - - $lockScope->appendChild($doc->createElementNS('DAV:','d:' . ($lock->scope==DAV\Locks\LockInfo::EXCLUSIVE?'exclusive':'shared'))); - - $lockType = $doc->createElementNS('DAV:','d:locktype'); - $activeLock->appendChild($lockType); - - $lockType->appendChild($doc->createElementNS('DAV:','d:write')); - - /* {DAV:}lockroot */ - if (!self::$hideLockRoot) { - $lockRoot = $doc->createElementNS('DAV:','d:lockroot'); - $activeLock->appendChild($lockRoot); - $href = $doc->createElementNS('DAV:','d:href'); - $href->appendChild($doc->createTextNode($server->getBaseUri() . $lock->uri)); - $lockRoot->appendChild($href); - } - - $activeLock->appendChild($doc->createElementNS('DAV:','d:depth',($lock->depth == DAV\Server::DEPTH_INFINITY?'infinity':$lock->depth))); - $activeLock->appendChild($doc->createElementNS('DAV:','d:timeout','Second-' . $lock->timeout)); - - if ($this->revealLockToken) { - $lockToken = $doc->createElementNS('DAV:','d:locktoken'); - $activeLock->appendChild($lockToken); - $lockToken->appendChild($doc->createElementNS('DAV:','d:href','opaquelocktoken:' . $lock->token)); - } - - $activeLock->appendChild($doc->createElementNS('DAV:','d:owner',$lock->owner)); - - } - - } - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Property/ResourceType.php b/vendor/sabre/dav/lib/Sabre/DAV/Property/ResourceType.php deleted file mode 100644 index 68134f3f9..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Property/ResourceType.php +++ /dev/null @@ -1,127 +0,0 @@ -resourceType = array(); - elseif ($resourceType === DAV\Server::NODE_DIRECTORY) - $this->resourceType = array('{DAV:}collection'); - elseif (is_array($resourceType)) - $this->resourceType = $resourceType; - else - $this->resourceType = array($resourceType); - - } - - /** - * serialize - * - * @param DAV\Server $server - * @param \DOMElement $prop - * @return void - */ - public function serialize(DAV\Server $server, \DOMElement $prop) { - - $propName = null; - $rt = $this->resourceType; - - foreach($rt as $resourceType) { - if (preg_match('/^{([^}]*)}(.*)$/',$resourceType,$propName)) { - - if (isset($server->xmlNamespaces[$propName[1]])) { - $prop->appendChild($prop->ownerDocument->createElement($server->xmlNamespaces[$propName[1]] . ':' . $propName[2])); - } else { - $prop->appendChild($prop->ownerDocument->createElementNS($propName[1],'custom:' . $propName[2])); - } - - } - } - - } - - /** - * Returns the values in clark-notation - * - * For example array('{DAV:}collection') - * - * @return array - */ - public function getValue() { - - return $this->resourceType; - - } - - /** - * Checks if the principal contains a certain value - * - * @param string $type - * @return bool - */ - public function is($type) { - - return in_array($type, $this->resourceType); - - } - - /** - * Adds a resourcetype value to this property - * - * @param string $type - * @return void - */ - public function add($type) { - - $this->resourceType[] = $type; - $this->resourceType = array_unique($this->resourceType); - - } - - /** - * Unserializes a DOM element into a ResourceType property. - * - * @param \DOMElement $dom - * @return DAV\Property\ResourceType - */ - static public function unserialize(\DOMElement $dom) { - - $value = array(); - foreach($dom->childNodes as $child) { - - $value[] = DAV\XMLUtil::toClarkNotation($child); - - } - - return new self($value); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Property/Response.php b/vendor/sabre/dav/lib/Sabre/DAV/Property/Response.php deleted file mode 100644 index 370abc26b..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Property/Response.php +++ /dev/null @@ -1,157 +0,0 @@ -href = $href; - $this->responseProperties = $responseProperties; - - } - - /** - * Returns the url - * - * @return string - */ - public function getHref() { - - return $this->href; - - } - - /** - * Returns the property list - * - * @return array - */ - public function getResponseProperties() { - - return $this->responseProperties; - - } - - /** - * serialize - * - * @param DAV\Server $server - * @param \DOMElement $dom - * @return void - */ - public function serialize(DAV\Server $server, \DOMElement $dom) { - - $document = $dom->ownerDocument; - $properties = $this->responseProperties; - - $xresponse = $document->createElement('d:response'); - $dom->appendChild($xresponse); - - $uri = DAV\URLUtil::encodePath($this->href); - - // Adding the baseurl to the beginning of the url - $uri = $server->getBaseUri() . $uri; - - $xresponse->appendChild($document->createElement('d:href',$uri)); - - // The properties variable is an array containing properties, grouped by - // HTTP status - foreach($properties as $httpStatus=>$propertyGroup) { - - // The 'href' is also in this array, and it's special cased. - // We will ignore it - if ($httpStatus=='href') continue; - - // If there are no properties in this group, we can also just carry on - if (!count($propertyGroup)) continue; - - $xpropstat = $document->createElement('d:propstat'); - $xresponse->appendChild($xpropstat); - - $xprop = $document->createElement('d:prop'); - $xpropstat->appendChild($xprop); - - $nsList = $server->xmlNamespaces; - - foreach($propertyGroup as $propertyName=>$propertyValue) { - - $propName = null; - preg_match('/^{([^}]*)}(.*)$/',$propertyName,$propName); - - // special case for empty namespaces - if ($propName[1]=='') { - - $currentProperty = $document->createElement($propName[2]); - $xprop->appendChild($currentProperty); - $currentProperty->setAttribute('xmlns',''); - - } else { - - if (!isset($nsList[$propName[1]])) { - $nsList[$propName[1]] = 'x' . count($nsList); - } - - // If the namespace was defined in the top-level xml namespaces, it means - // there was already a namespace declaration, and we don't have to worry about it. - if (isset($server->xmlNamespaces[$propName[1]])) { - $currentProperty = $document->createElement($nsList[$propName[1]] . ':' . $propName[2]); - } else { - $currentProperty = $document->createElementNS($propName[1],$nsList[$propName[1]].':' . $propName[2]); - } - $xprop->appendChild($currentProperty); - - } - - if (is_scalar($propertyValue)) { - $text = $document->createTextNode($propertyValue); - $currentProperty->appendChild($text); - } elseif ($propertyValue instanceof DAV\PropertyInterface) { - $propertyValue->serialize($server,$currentProperty); - } elseif (!is_null($propertyValue)) { - throw new DAV\Exception('Unknown property value type: ' . gettype($propertyValue) . ' for property: ' . $propertyName); - } - - } - - $xpropstat->appendChild($document->createElement('d:status',$server->httpResponse->getStatusMessage($httpStatus))); - - } - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Property/ResponseList.php b/vendor/sabre/dav/lib/Sabre/DAV/Property/ResponseList.php deleted file mode 100644 index 9db6cbbf5..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Property/ResponseList.php +++ /dev/null @@ -1,59 +0,0 @@ -responses = $responses; - - } - - /** - * serialize - * - * @param DAV\Server $server - * @param \DOMElement $dom - * @return void - */ - public function serialize(DAV\Server $server,\DOMElement $dom) { - - foreach($this->responses as $response) { - $response->serialize($server, $dom); - } - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Property/SupportedLock.php b/vendor/sabre/dav/lib/Sabre/DAV/Property/SupportedLock.php deleted file mode 100644 index 035c2f330..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Property/SupportedLock.php +++ /dev/null @@ -1,78 +0,0 @@ -supportsLocks = $supportsLocks; - - } - - /** - * serialize - * - * @param DAV\Server $server - * @param \DOMElement $prop - * @return void - */ - public function serialize(DAV\Server $server,\DOMElement $prop) { - - $doc = $prop->ownerDocument; - - if (!$this->supportsLocks) return null; - - $lockEntry1 = $doc->createElement('d:lockentry'); - $lockEntry2 = $doc->createElement('d:lockentry'); - - $prop->appendChild($lockEntry1); - $prop->appendChild($lockEntry2); - - $lockScope1 = $doc->createElement('d:lockscope'); - $lockScope2 = $doc->createElement('d:lockscope'); - $lockType1 = $doc->createElement('d:locktype'); - $lockType2 = $doc->createElement('d:locktype'); - - $lockEntry1->appendChild($lockScope1); - $lockEntry1->appendChild($lockType1); - $lockEntry2->appendChild($lockScope2); - $lockEntry2->appendChild($lockType2); - - $lockScope1->appendChild($doc->createElement('d:exclusive')); - $lockScope2->appendChild($doc->createElement('d:shared')); - - $lockType1->appendChild($doc->createElement('d:write')); - $lockType2->appendChild($doc->createElement('d:write')); - - //$frag->appendXML(''); - //$frag->appendXML(''); - - } - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Property/SupportedReportSet.php b/vendor/sabre/dav/lib/Sabre/DAV/Property/SupportedReportSet.php deleted file mode 100644 index a8a90bb18..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Property/SupportedReportSet.php +++ /dev/null @@ -1,111 +0,0 @@ -addReport($reports); - - } - - /** - * Adds a report to this property - * - * The report must be a string in clark-notation. - * Multiple reports can be specified as an array. - * - * @param mixed $report - * @return void - */ - public function addReport($report) { - - if (!is_array($report)) $report = array($report); - - foreach($report as $r) { - - if (!preg_match('/^{([^}]*)}(.*)$/',$r)) - throw new DAV\Exception('Reportname must be in clark-notation'); - - $this->reports[] = $r; - - } - - } - - /** - * Returns the list of supported reports - * - * @return array - */ - public function getValue() { - - return $this->reports; - - } - - /** - * Serializes the node - * - * @param DAV\Server $server - * @param \DOMElement $prop - * @return void - */ - public function serialize(DAV\Server $server, \DOMElement $prop) { - - foreach($this->reports as $reportName) { - - $supportedReport = $prop->ownerDocument->createElement('d:supported-report'); - $prop->appendChild($supportedReport); - - $report = $prop->ownerDocument->createElement('d:report'); - $supportedReport->appendChild($report); - - preg_match('/^{([^}]*)}(.*)$/',$reportName,$matches); - - list(, $namespace, $element) = $matches; - - $prefix = isset($server->xmlNamespaces[$namespace])?$server->xmlNamespaces[$namespace]:null; - - if ($prefix) { - $report->appendChild($prop->ownerDocument->createElement($prefix . ':' . $element)); - } else { - $report->appendChild($prop->ownerDocument->createElementNS($namespace, 'x:' . $element)); - } - - } - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/PropertyInterface.php b/vendor/sabre/dav/lib/Sabre/DAV/PropertyInterface.php deleted file mode 100644 index 2fb0d7db6..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/PropertyInterface.php +++ /dev/null @@ -1,21 +0,0 @@ - 'd', - 'http://sabredav.org/ns' => 's', - ); - - /** - * The propertymap can be used to map properties from - * requests to property classes. - * - * @var array - */ - public $propertyMap = array( - '{DAV:}resourcetype' => 'Sabre\\DAV\\Property\\ResourceType', - ); - - public $protectedProperties = array( - // RFC4918 - '{DAV:}getcontentlength', - '{DAV:}getetag', - '{DAV:}getlastmodified', - '{DAV:}lockdiscovery', - '{DAV:}supportedlock', - - // RFC4331 - '{DAV:}quota-available-bytes', - '{DAV:}quota-used-bytes', - - // RFC3744 - '{DAV:}supported-privilege-set', - '{DAV:}current-user-privilege-set', - '{DAV:}acl', - '{DAV:}acl-restrictions', - '{DAV:}inherited-acl-set', - - ); - - /** - * This is a flag that allow or not showing file, line and code - * of the exception in the returned XML - * - * @var bool - */ - public $debugExceptions = false; - - /** - * This property allows you to automatically add the 'resourcetype' value - * based on a node's classname or interface. - * - * The preset ensures that {DAV:}collection is automaticlly added for nodes - * implementing Sabre\DAV\ICollection. - * - * @var array - */ - public $resourceTypeMapping = array( - 'Sabre\\DAV\\ICollection' => '{DAV:}collection', - ); - - /** - * If this setting is turned off, SabreDAV's version number will be hidden - * from various places. - * - * Some people feel this is a good security measure. - * - * @var bool - */ - static public $exposeVersion = true; - - /** - * Sets up the server - * - * If a Sabre\DAV\Tree object is passed as an argument, it will - * use it as the directory tree. If a Sabre\DAV\INode is passed, it - * will create a Sabre\DAV\ObjectTree and use the node as the root. - * - * If nothing is passed, a Sabre\DAV\SimpleCollection is created in - * a Sabre\DAV\ObjectTree. - * - * If an array is passed, we automatically create a root node, and use - * the nodes in the array as top-level children. - * - * @param Tree|INode|array|null $treeOrNode The tree object - */ - public function __construct($treeOrNode = null) { - - if ($treeOrNode instanceof Tree) { - $this->tree = $treeOrNode; - } elseif ($treeOrNode instanceof INode) { - $this->tree = new ObjectTree($treeOrNode); - } elseif (is_array($treeOrNode)) { - - // If it's an array, a list of nodes was passed, and we need to - // create the root node. - foreach($treeOrNode as $node) { - if (!($node instanceof INode)) { - throw new Exception('Invalid argument passed to constructor. If you\'re passing an array, all the values must implement Sabre\\DAV\\INode'); - } - } - - $root = new SimpleCollection('root', $treeOrNode); - $this->tree = new ObjectTree($root); - - } elseif (is_null($treeOrNode)) { - $root = new SimpleCollection('root'); - $this->tree = new ObjectTree($root); - } else { - throw new Exception('Invalid argument passed to constructor. Argument must either be an instance of Sabre\\DAV\\Tree, Sabre\\DAV\\INode, an array or null'); - } - $this->httpResponse = new HTTP\Response(); - $this->httpRequest = new HTTP\Request(); - - } - - /** - * Starts the DAV Server - * - * @return void - */ - public function exec() { - - try { - - // If nginx (pre-1.2) is used as a proxy server, and SabreDAV as an - // origin, we must make sure we send back HTTP/1.0 if this was - // requested. - // This is mainly because nginx doesn't support Chunked Transfer - // Encoding, and this forces the webserver SabreDAV is running on, - // to buffer entire responses to calculate Content-Length. - $this->httpResponse->defaultHttpVersion = $this->httpRequest->getHTTPVersion(); - - $this->invokeMethod($this->httpRequest->getMethod(), $this->getRequestUri()); - - } catch (Exception $e) { - - try { - $this->broadcastEvent('exception', array($e)); - } catch (Exception $ignore) { - } - $DOM = new \DOMDocument('1.0','utf-8'); - $DOM->formatOutput = true; - - $error = $DOM->createElementNS('DAV:','d:error'); - $error->setAttribute('xmlns:s',self::NS_SABREDAV); - $DOM->appendChild($error); - - $h = function($v) { - - return htmlspecialchars($v, ENT_NOQUOTES, 'UTF-8'); - - }; - - $error->appendChild($DOM->createElement('s:exception',$h(get_class($e)))); - $error->appendChild($DOM->createElement('s:message',$h($e->getMessage()))); - if ($this->debugExceptions) { - $error->appendChild($DOM->createElement('s:file',$h($e->getFile()))); - $error->appendChild($DOM->createElement('s:line',$h($e->getLine()))); - $error->appendChild($DOM->createElement('s:code',$h($e->getCode()))); - $error->appendChild($DOM->createElement('s:stacktrace',$h($e->getTraceAsString()))); - - } - if (self::$exposeVersion) { - $error->appendChild($DOM->createElement('s:sabredav-version',$h(Version::VERSION))); - } - - if($e instanceof Exception) { - - $httpCode = $e->getHTTPCode(); - $e->serialize($this,$error); - $headers = $e->getHTTPHeaders($this); - - } else { - - $httpCode = 500; - $headers = array(); - - } - $headers['Content-Type'] = 'application/xml; charset=utf-8'; - - $this->httpResponse->sendStatus($httpCode); - $this->httpResponse->setHeaders($headers); - $this->httpResponse->sendBody($DOM->saveXML()); - - } - - } - - /** - * Sets the base server uri - * - * @param string $uri - * @return void - */ - public function setBaseUri($uri) { - - // If the baseUri does not end with a slash, we must add it - if ($uri[strlen($uri)-1]!=='/') - $uri.='/'; - - $this->baseUri = $uri; - - } - - /** - * Returns the base responding uri - * - * @return string - */ - public function getBaseUri() { - - if (is_null($this->baseUri)) $this->baseUri = $this->guessBaseUri(); - return $this->baseUri; - - } - - /** - * This method attempts to detect the base uri. - * Only the PATH_INFO variable is considered. - * - * If this variable is not set, the root (/) is assumed. - * - * @return string - */ - public function guessBaseUri() { - - $pathInfo = $this->httpRequest->getRawServerValue('PATH_INFO'); - $uri = $this->httpRequest->getRawServerValue('REQUEST_URI'); - - // If PATH_INFO is found, we can assume it's accurate. - if (!empty($pathInfo)) { - - // We need to make sure we ignore the QUERY_STRING part - if ($pos = strpos($uri,'?')) - $uri = substr($uri,0,$pos); - - // PATH_INFO is only set for urls, such as: /example.php/path - // in that case PATH_INFO contains '/path'. - // Note that REQUEST_URI is percent encoded, while PATH_INFO is - // not, Therefore they are only comparable if we first decode - // REQUEST_INFO as well. - $decodedUri = URLUtil::decodePath($uri); - - // A simple sanity check: - if(substr($decodedUri,strlen($decodedUri)-strlen($pathInfo))===$pathInfo) { - $baseUri = substr($decodedUri,0,strlen($decodedUri)-strlen($pathInfo)); - return rtrim($baseUri,'/') . '/'; - } - - throw new Exception('The REQUEST_URI ('. $uri . ') did not end with the contents of PATH_INFO (' . $pathInfo . '). This server might be misconfigured.'); - - } - - // The last fallback is that we're just going to assume the server root. - return '/'; - - } - - /** - * Adds a plugin to the server - * - * For more information, console the documentation of Sabre\DAV\ServerPlugin - * - * @param ServerPlugin $plugin - * @return void - */ - public function addPlugin(ServerPlugin $plugin) { - - $this->plugins[$plugin->getPluginName()] = $plugin; - $plugin->initialize($this); - - } - - /** - * Returns an initialized plugin by it's name. - * - * This function returns null if the plugin was not found. - * - * @param string $name - * @return ServerPlugin - */ - public function getPlugin($name) { - - if (isset($this->plugins[$name])) - return $this->plugins[$name]; - - // This is a fallback and deprecated. - foreach($this->plugins as $plugin) { - if (get_class($plugin)===$name) return $plugin; - } - - return null; - - } - - /** - * Returns all plugins - * - * @return array - */ - public function getPlugins() { - - return $this->plugins; - - } - - - /** - * Subscribe to an event. - * - * When the event is triggered, we'll call all the specified callbacks. - * It is possible to control the order of the callbacks through the - * priority argument. - * - * This is for example used to make sure that the authentication plugin - * is triggered before anything else. If it's not needed to change this - * number, it is recommended to ommit. - * - * @param string $event - * @param callback $callback - * @param int $priority - * @return void - */ - public function subscribeEvent($event, $callback, $priority = 100) { - - if (!isset($this->eventSubscriptions[$event])) { - $this->eventSubscriptions[$event] = array(); - } - while(isset($this->eventSubscriptions[$event][$priority])) $priority++; - $this->eventSubscriptions[$event][$priority] = $callback; - ksort($this->eventSubscriptions[$event]); - - } - - /** - * Broadcasts an event - * - * This method will call all subscribers. If one of the subscribers returns false, the process stops. - * - * The arguments parameter will be sent to all subscribers - * - * @param string $eventName - * @param array $arguments - * @return bool - */ - public function broadcastEvent($eventName,$arguments = array()) { - - if (isset($this->eventSubscriptions[$eventName])) { - - foreach($this->eventSubscriptions[$eventName] as $subscriber) { - - $result = call_user_func_array($subscriber,$arguments); - if ($result===false) return false; - - } - - } - - return true; - - } - - /** - * Handles a http request, and execute a method based on its name - * - * @param string $method - * @param string $uri - * @return void - */ - public function invokeMethod($method, $uri) { - - $method = strtoupper($method); - - if (!$this->broadcastEvent('beforeMethod',array($method, $uri))) return; - - // Make sure this is a HTTP method we support - $internalMethods = array( - 'OPTIONS', - 'GET', - 'HEAD', - 'DELETE', - 'PROPFIND', - 'MKCOL', - 'PUT', - 'PROPPATCH', - 'COPY', - 'MOVE', - 'REPORT' - ); - - if (in_array($method,$internalMethods)) { - - call_user_func(array($this,'http' . $method), $uri); - - } else { - - if ($this->broadcastEvent('unknownMethod',array($method, $uri))) { - // Unsupported method - throw new Exception\NotImplemented('There was no handler found for this "' . $method . '" method'); - } - - } - - } - - // {{{ HTTP Method implementations - - /** - * HTTP OPTIONS - * - * @param string $uri - * @return void - */ - protected function httpOptions($uri) { - - $methods = $this->getAllowedMethods($uri); - - $this->httpResponse->setHeader('Allow',strtoupper(implode(', ',$methods))); - $features = array('1','3', 'extended-mkcol'); - - foreach($this->plugins as $plugin) $features = array_merge($features,$plugin->getFeatures()); - - $this->httpResponse->setHeader('DAV',implode(', ',$features)); - $this->httpResponse->setHeader('MS-Author-Via','DAV'); - $this->httpResponse->setHeader('Accept-Ranges','bytes'); - if (self::$exposeVersion) { - $this->httpResponse->setHeader('X-Sabre-Version',Version::VERSION); - } - $this->httpResponse->setHeader('Content-Length',0); - $this->httpResponse->sendStatus(200); - - } - - /** - * HTTP GET - * - * This method simply fetches the contents of a uri, like normal - * - * @param string $uri - * @return bool - */ - protected function httpGet($uri) { - - $node = $this->tree->getNodeForPath($uri,0); - - if (!$this->checkPreconditions(true)) return false; - if (!$node instanceof IFile) throw new Exception\NotImplemented('GET is only implemented on File objects'); - - $body = $node->get(); - - // Converting string into stream, if needed. - if (is_string($body)) { - $stream = fopen('php://temp','r+'); - fwrite($stream,$body); - rewind($stream); - $body = $stream; - } - - /* - * TODO: getetag, getlastmodified, getsize should also be used using - * this method - */ - $httpHeaders = $this->getHTTPHeaders($uri); - - /* ContentType needs to get a default, because many webservers will otherwise - * default to text/html, and we don't want this for security reasons. - */ - if (!isset($httpHeaders['Content-Type'])) { - $httpHeaders['Content-Type'] = 'application/octet-stream'; - } - - - if (isset($httpHeaders['Content-Length'])) { - - $nodeSize = $httpHeaders['Content-Length']; - - // Need to unset Content-Length, because we'll handle that during figuring out the range - unset($httpHeaders['Content-Length']); - - } else { - $nodeSize = null; - } - - $this->httpResponse->setHeaders($httpHeaders); - - $range = $this->getHTTPRange(); - $ifRange = $this->httpRequest->getHeader('If-Range'); - $ignoreRangeHeader = false; - - // If ifRange is set, and range is specified, we first need to check - // the precondition. - if ($nodeSize && $range && $ifRange) { - - // if IfRange is parsable as a date we'll treat it as a DateTime - // otherwise, we must treat it as an etag. - try { - $ifRangeDate = new \DateTime($ifRange); - - // It's a date. We must check if the entity is modified since - // the specified date. - if (!isset($httpHeaders['Last-Modified'])) $ignoreRangeHeader = true; - else { - $modified = new \DateTime($httpHeaders['Last-Modified']); - if($modified > $ifRangeDate) $ignoreRangeHeader = true; - } - - } catch (\Exception $e) { - - // It's an entity. We can do a simple comparison. - if (!isset($httpHeaders['ETag'])) $ignoreRangeHeader = true; - elseif ($httpHeaders['ETag']!==$ifRange) $ignoreRangeHeader = true; - } - } - - // We're only going to support HTTP ranges if the backend provided a filesize - if (!$ignoreRangeHeader && $nodeSize && $range) { - - // Determining the exact byte offsets - if (!is_null($range[0])) { - - $start = $range[0]; - $end = $range[1]?$range[1]:$nodeSize-1; - if($start >= $nodeSize) - throw new Exception\RequestedRangeNotSatisfiable('The start offset (' . $range[0] . ') exceeded the size of the entity (' . $nodeSize . ')'); - - if($end < $start) throw new Exception\RequestedRangeNotSatisfiable('The end offset (' . $range[1] . ') is lower than the start offset (' . $range[0] . ')'); - if($end >= $nodeSize) $end = $nodeSize-1; - - } else { - - $start = $nodeSize-$range[1]; - $end = $nodeSize-1; - - if ($start<0) $start = 0; - - } - - // New read/write stream - $newStream = fopen('php://temp','r+'); - - // stream_copy_to_stream() has a bug/feature: the `whence` argument - // is interpreted as SEEK_SET (count from absolute offset 0), while - // for a stream it should be SEEK_CUR (count from current offset). - // If a stream is nonseekable, the function fails. So we *emulate* - // the correct behaviour with fseek(): - if ($start > 0) { - if (($curOffs = ftell($body)) === false) $curOffs = 0; - fseek($body, $start - $curOffs, SEEK_CUR); - } - stream_copy_to_stream($body, $newStream, $end-$start+1); - rewind($newStream); - - $this->httpResponse->setHeader('Content-Length', $end-$start+1); - $this->httpResponse->setHeader('Content-Range','bytes ' . $start . '-' . $end . '/' . $nodeSize); - $this->httpResponse->sendStatus(206); - $this->httpResponse->sendBody($newStream); - - - } else { - - if ($nodeSize) $this->httpResponse->setHeader('Content-Length',$nodeSize); - $this->httpResponse->sendStatus(200); - $this->httpResponse->sendBody($body); - - } - - } - - /** - * HTTP HEAD - * - * This method is normally used to take a peak at a url, and only get the HTTP response headers, without the body - * This is used by clients to determine if a remote file was changed, so they can use a local cached version, instead of downloading it again - * - * @param string $uri - * @return void - */ - protected function httpHead($uri) { - - $node = $this->tree->getNodeForPath($uri); - /* This information is only collection for File objects. - * Ideally we want to throw 405 Method Not Allowed for every - * non-file, but MS Office does not like this - */ - if ($node instanceof IFile) { - $headers = $this->getHTTPHeaders($this->getRequestUri()); - if (!isset($headers['Content-Type'])) { - $headers['Content-Type'] = 'application/octet-stream'; - } - $this->httpResponse->setHeaders($headers); - } - $this->httpResponse->sendStatus(200); - - } - - /** - * HTTP Delete - * - * The HTTP delete method, deletes a given uri - * - * @param string $uri - * @return void - */ - protected function httpDelete($uri) { - - // Checking If-None-Match and related headers. - if (!$this->checkPreconditions()) return; - - if (!$this->broadcastEvent('beforeUnbind',array($uri))) return; - $this->tree->delete($uri); - $this->broadcastEvent('afterUnbind',array($uri)); - - $this->httpResponse->sendStatus(204); - $this->httpResponse->setHeader('Content-Length','0'); - - } - - - /** - * WebDAV PROPFIND - * - * This WebDAV method requests information about an uri resource, or a list of resources - * If a client wants to receive the properties for a single resource it will add an HTTP Depth: header with a 0 value - * If the value is 1, it means that it also expects a list of sub-resources (e.g.: files in a directory) - * - * The request body contains an XML data structure that has a list of properties the client understands - * The response body is also an xml document, containing information about every uri resource and the requested properties - * - * It has to return a HTTP 207 Multi-status status code - * - * @param string $uri - * @return void - */ - protected function httpPropfind($uri) { - - $requestedProperties = $this->parsePropFindRequest($this->httpRequest->getBody(true)); - - $depth = $this->getHTTPDepth(1); - // The only two options for the depth of a propfind is 0 or 1 - if ($depth!=0) $depth = 1; - - $newProperties = $this->getPropertiesForPath($uri,$requestedProperties,$depth); - - // This is a multi-status response - $this->httpResponse->sendStatus(207); - $this->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); - $this->httpResponse->setHeader('Vary','Brief,Prefer'); - - // Normally this header is only needed for OPTIONS responses, however.. - // iCal seems to also depend on these being set for PROPFIND. Since - // this is not harmful, we'll add it. - $features = array('1','3', 'extended-mkcol'); - foreach($this->plugins as $plugin) $features = array_merge($features,$plugin->getFeatures()); - $this->httpResponse->setHeader('DAV',implode(', ',$features)); - - $prefer = $this->getHTTPPrefer(); - $minimal = $prefer['return-minimal']; - - $data = $this->generateMultiStatus($newProperties, $minimal); - $this->httpResponse->sendBody($data); - - } - - /** - * WebDAV PROPPATCH - * - * This method is called to update properties on a Node. The request is an XML body with all the mutations. - * In this XML body it is specified which properties should be set/updated and/or deleted - * - * @param string $uri - * @return void - */ - protected function httpPropPatch($uri) { - - $newProperties = $this->parsePropPatchRequest($this->httpRequest->getBody(true)); - - $result = $this->updateProperties($uri, $newProperties); - - $prefer = $this->getHTTPPrefer(); - $this->httpResponse->setHeader('Vary','Brief,Prefer'); - - if ($prefer['return-minimal']) { - - // If return-minimal is specified, we only have to check if the - // request was succesful, and don't need to return the - // multi-status. - $ok = true; - foreach($result as $code=>$prop) { - if ((int)$code > 299) { - $ok = false; - } - } - - if ($ok) { - - $this->httpResponse->sendStatus(204); - return; - - } - - } - - $this->httpResponse->sendStatus(207); - $this->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); - - $this->httpResponse->sendBody( - $this->generateMultiStatus(array($result)) - ); - - } - - /** - * HTTP PUT method - * - * This HTTP method updates a file, or creates a new one. - * - * If a new resource was created, a 201 Created status code should be returned. If an existing resource is updated, it's a 204 No Content - * - * @param string $uri - * @return bool - */ - protected function httpPut($uri) { - - $body = $this->httpRequest->getBody(); - - // Intercepting Content-Range - if ($this->httpRequest->getHeader('Content-Range')) { - /** - Content-Range is dangerous for PUT requests: PUT per definition - stores a full resource. draft-ietf-httpbis-p2-semantics-15 says - in section 7.6: - An origin server SHOULD reject any PUT request that contains a - Content-Range header field, since it might be misinterpreted as - partial content (or might be partial content that is being mistakenly - PUT as a full representation). Partial content updates are possible - by targeting a separately identified resource with state that - overlaps a portion of the larger resource, or by using a different - method that has been specifically defined for partial updates (for - example, the PATCH method defined in [RFC5789]). - This clarifies RFC2616 section 9.6: - The recipient of the entity MUST NOT ignore any Content-* - (e.g. Content-Range) headers that it does not understand or implement - and MUST return a 501 (Not Implemented) response in such cases. - OTOH is a PUT request with a Content-Range currently the only way to - continue an aborted upload request and is supported by curl, mod_dav, - Tomcat and others. Since some clients do use this feature which results - in unexpected behaviour (cf PEAR::HTTP_WebDAV_Client 1.0.1), we reject - all PUT requests with a Content-Range for now. - */ - - throw new Exception\NotImplemented('PUT with Content-Range is not allowed.'); - } - - // Intercepting the Finder problem - if (($expected = $this->httpRequest->getHeader('X-Expected-Entity-Length')) && $expected > 0) { - - /** - Many webservers will not cooperate well with Finder PUT requests, - because it uses 'Chunked' transfer encoding for the request body. - - The symptom of this problem is that Finder sends files to the - server, but they arrive as 0-length files in PHP. - - If we don't do anything, the user might think they are uploading - files successfully, but they end up empty on the server. Instead, - we throw back an error if we detect this. - - The reason Finder uses Chunked, is because it thinks the files - might change as it's being uploaded, and therefore the - Content-Length can vary. - - Instead it sends the X-Expected-Entity-Length header with the size - of the file at the very start of the request. If this header is set, - but we don't get a request body we will fail the request to - protect the end-user. - */ - - // Only reading first byte - $firstByte = fread($body,1); - if (strlen($firstByte)!==1) { - throw new Exception\Forbidden('This server is not compatible with OS/X finder. Consider using a different WebDAV client or webserver.'); - } - - // The body needs to stay intact, so we copy everything to a - // temporary stream. - - $newBody = fopen('php://temp','r+'); - fwrite($newBody,$firstByte); - stream_copy_to_stream($body, $newBody); - rewind($newBody); - - $body = $newBody; - - } - - // Checking If-None-Match and related headers. - if (!$this->checkPreconditions()) return; - - if ($this->tree->nodeExists($uri)) { - - $node = $this->tree->getNodeForPath($uri); - - // If the node is a collection, we'll deny it - if (!($node instanceof IFile)) throw new Exception\Conflict('PUT is not allowed on non-files.'); - if (!$this->broadcastEvent('beforeWriteContent',array($uri, $node, &$body))) return false; - - $etag = $node->put($body); - - $this->broadcastEvent('afterWriteContent',array($uri, $node)); - - $this->httpResponse->setHeader('Content-Length','0'); - if ($etag) $this->httpResponse->setHeader('ETag',$etag); - $this->httpResponse->sendStatus(204); - - } else { - - $etag = null; - // If we got here, the resource didn't exist yet. - if (!$this->createFile($this->getRequestUri(),$body,$etag)) { - // For one reason or another the file was not created. - return; - } - - $this->httpResponse->setHeader('Content-Length','0'); - if ($etag) $this->httpResponse->setHeader('ETag', $etag); - $this->httpResponse->sendStatus(201); - - } - - } - - - /** - * WebDAV MKCOL - * - * The MKCOL method is used to create a new collection (directory) on the server - * - * @param string $uri - * @return void - */ - protected function httpMkcol($uri) { - - $requestBody = $this->httpRequest->getBody(true); - - if ($requestBody) { - - $contentType = $this->httpRequest->getHeader('Content-Type'); - if (strpos($contentType,'application/xml')!==0 && strpos($contentType,'text/xml')!==0) { - - // We must throw 415 for unsupported mkcol bodies - throw new Exception\UnsupportedMediaType('The request body for the MKCOL request must have an xml Content-Type'); - - } - - $dom = XMLUtil::loadDOMDocument($requestBody); - if (XMLUtil::toClarkNotation($dom->firstChild)!=='{DAV:}mkcol') { - - // We must throw 415 for unsupported mkcol bodies - throw new Exception\UnsupportedMediaType('The request body for the MKCOL request must be a {DAV:}mkcol request construct.'); - - } - - $properties = array(); - foreach($dom->firstChild->childNodes as $childNode) { - - if (XMLUtil::toClarkNotation($childNode)!=='{DAV:}set') continue; - $properties = array_merge($properties, XMLUtil::parseProperties($childNode, $this->propertyMap)); - - } - if (!isset($properties['{DAV:}resourcetype'])) - throw new Exception\BadRequest('The mkcol request must include a {DAV:}resourcetype property'); - - $resourceType = $properties['{DAV:}resourcetype']->getValue(); - unset($properties['{DAV:}resourcetype']); - - } else { - - $properties = array(); - $resourceType = array('{DAV:}collection'); - - } - - $result = $this->createCollection($uri, $resourceType, $properties); - - if (is_array($result)) { - $this->httpResponse->sendStatus(207); - $this->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); - - $this->httpResponse->sendBody( - $this->generateMultiStatus(array($result)) - ); - - } else { - $this->httpResponse->setHeader('Content-Length','0'); - $this->httpResponse->sendStatus(201); - } - - } - - /** - * WebDAV HTTP MOVE method - * - * This method moves one uri to a different uri. A lot of the actual request processing is done in getCopyMoveInfo - * - * @param string $uri - * @return bool - */ - protected function httpMove($uri) { - - $moveInfo = $this->getCopyAndMoveInfo(); - - // If the destination is part of the source tree, we must fail - if ($moveInfo['destination']==$uri) - throw new Exception\Forbidden('Source and destination uri are identical.'); - - if ($moveInfo['destinationExists']) { - - if (!$this->broadcastEvent('beforeUnbind',array($moveInfo['destination']))) return false; - $this->tree->delete($moveInfo['destination']); - $this->broadcastEvent('afterUnbind',array($moveInfo['destination'])); - - } - - if (!$this->broadcastEvent('beforeUnbind',array($uri))) return false; - if (!$this->broadcastEvent('beforeBind',array($moveInfo['destination']))) return false; - $this->tree->move($uri,$moveInfo['destination']); - $this->broadcastEvent('afterUnbind',array($uri)); - $this->broadcastEvent('afterBind',array($moveInfo['destination'])); - - // If a resource was overwritten we should send a 204, otherwise a 201 - $this->httpResponse->setHeader('Content-Length','0'); - $this->httpResponse->sendStatus($moveInfo['destinationExists']?204:201); - - } - - /** - * WebDAV HTTP COPY method - * - * This method copies one uri to a different uri, and works much like the MOVE request - * A lot of the actual request processing is done in getCopyMoveInfo - * - * @param string $uri - * @return bool - */ - protected function httpCopy($uri) { - - $copyInfo = $this->getCopyAndMoveInfo(); - // If the destination is part of the source tree, we must fail - if ($copyInfo['destination']==$uri) - throw new Exception\Forbidden('Source and destination uri are identical.'); - - if ($copyInfo['destinationExists']) { - if (!$this->broadcastEvent('beforeUnbind',array($copyInfo['destination']))) return false; - $this->tree->delete($copyInfo['destination']); - - } - if (!$this->broadcastEvent('beforeBind',array($copyInfo['destination']))) return false; - $this->tree->copy($uri,$copyInfo['destination']); - $this->broadcastEvent('afterBind',array($copyInfo['destination'])); - - // If a resource was overwritten we should send a 204, otherwise a 201 - $this->httpResponse->setHeader('Content-Length','0'); - $this->httpResponse->sendStatus($copyInfo['destinationExists']?204:201); - - } - - - - /** - * HTTP REPORT method implementation - * - * Although the REPORT method is not part of the standard WebDAV spec (it's from rfc3253) - * It's used in a lot of extensions, so it made sense to implement it into the core. - * - * @param string $uri - * @return void - */ - protected function httpReport($uri) { - - $body = $this->httpRequest->getBody(true); - $dom = XMLUtil::loadDOMDocument($body); - - $reportName = XMLUtil::toClarkNotation($dom->firstChild); - - if ($this->broadcastEvent('report',array($reportName,$dom, $uri))) { - - // If broadcastEvent returned true, it means the report was not supported - throw new Exception\ReportNotSupported(); - - } - - } - - // }}} - // {{{ HTTP/WebDAV protocol helpers - - /** - * Returns an array with all the supported HTTP methods for a specific uri. - * - * @param string $uri - * @return array - */ - public function getAllowedMethods($uri) { - - $methods = array( - 'OPTIONS', - 'GET', - 'HEAD', - 'DELETE', - 'PROPFIND', - 'PUT', - 'PROPPATCH', - 'COPY', - 'MOVE', - 'REPORT' - ); - - // The MKCOL is only allowed on an unmapped uri - try { - $this->tree->getNodeForPath($uri); - } catch (Exception\NotFound $e) { - $methods[] = 'MKCOL'; - } - - // We're also checking if any of the plugins register any new methods - foreach($this->plugins as $plugin) $methods = array_merge($methods, $plugin->getHTTPMethods($uri)); - array_unique($methods); - - return $methods; - - } - - /** - * Gets the uri for the request, keeping the base uri into consideration - * - * @return string - */ - public function getRequestUri() { - - return $this->calculateUri($this->httpRequest->getUri()); - - } - - /** - * Calculates the uri for a request, making sure that the base uri is stripped out - * - * @param string $uri - * @throws Exception\Forbidden A permission denied exception is thrown whenever there was an attempt to supply a uri outside of the base uri - * @return string - */ - public function calculateUri($uri) { - - if ($uri[0]!='/' && strpos($uri,'://')) { - - $uri = parse_url($uri,PHP_URL_PATH); - - } - - $uri = str_replace('//','/',$uri); - - if (strpos($uri,$this->getBaseUri())===0) { - - return trim(URLUtil::decodePath(substr($uri,strlen($this->getBaseUri()))),'/'); - - // A special case, if the baseUri was accessed without a trailing - // slash, we'll accept it as well. - } elseif ($uri.'/' === $this->getBaseUri()) { - - return ''; - - } else { - - throw new Exception\Forbidden('Requested uri (' . $uri . ') is out of base uri (' . $this->getBaseUri() . ')'); - - } - - } - - /** - * Returns the HTTP depth header - * - * This method returns the contents of the HTTP depth request header. If the depth header was 'infinity' it will return the Sabre\DAV\Server::DEPTH_INFINITY object - * It is possible to supply a default depth value, which is used when the depth header has invalid content, or is completely non-existent - * - * @param mixed $default - * @return int - */ - public function getHTTPDepth($default = self::DEPTH_INFINITY) { - - // If its not set, we'll grab the default - $depth = $this->httpRequest->getHeader('Depth'); - - if (is_null($depth)) return $default; - - if ($depth == 'infinity') return self::DEPTH_INFINITY; - - - // If its an unknown value. we'll grab the default - if (!ctype_digit($depth)) return $default; - - return (int)$depth; - - } - - /** - * Returns the HTTP range header - * - * This method returns null if there is no well-formed HTTP range request - * header or array($start, $end). - * - * The first number is the offset of the first byte in the range. - * The second number is the offset of the last byte in the range. - * - * If the second offset is null, it should be treated as the offset of the last byte of the entity - * If the first offset is null, the second offset should be used to retrieve the last x bytes of the entity - * - * @return array|null - */ - public function getHTTPRange() { - - $range = $this->httpRequest->getHeader('range'); - if (is_null($range)) return null; - - // Matching "Range: bytes=1234-5678: both numbers are optional - - if (!preg_match('/^bytes=([0-9]*)-([0-9]*)$/i',$range,$matches)) return null; - - if ($matches[1]==='' && $matches[2]==='') return null; - - return array( - $matches[1]!==''?$matches[1]:null, - $matches[2]!==''?$matches[2]:null, - ); - - } - - /** - * Returns the HTTP Prefer header information. - * - * The prefer header is defined in: - * http://tools.ietf.org/html/draft-snell-http-prefer-14 - * - * This method will return an array with options. - * - * Currently, the following options may be returned: - * array( - * 'return-asynch' => true, - * 'return-minimal' => true, - * 'return-representation' => true, - * 'wait' => 30, - * 'strict' => true, - * 'lenient' => true, - * ) - * - * This method also supports the Brief header, and will also return - * 'return-minimal' if the brief header was set to 't'. - * - * For the boolean options, false will be returned if the headers are not - * specified. For the integer options it will be 'null'. - * - * @return array - */ - public function getHTTPPrefer() { - - $result = array( - 'return-asynch' => false, - 'return-minimal' => false, - 'return-representation' => false, - 'wait' => null, - 'strict' => false, - 'lenient' => false, - ); - - if ($prefer = $this->httpRequest->getHeader('Prefer')) { - - $parameters = array_map('trim', - explode(',', $prefer) - ); - - foreach($parameters as $parameter) { - - // Right now our regex only supports the tokens actually - // specified in the draft. We may need to expand this if new - // tokens get registered. - if(!preg_match('/^(?P[a-z0-9-]+)(?:=(?P[0-9]+))?$/', $parameter, $matches)) { - continue; - } - - switch($matches['token']) { - - case 'return-asynch' : - case 'return-minimal' : - case 'return-representation' : - case 'strict' : - case 'lenient' : - $result[$matches['token']] = true; - break; - case 'wait' : - $result[$matches['token']] = $matches['value']; - break; - - } - - } - - } - - if ($this->httpRequest->getHeader('Brief')=='t') { - $result['return-minimal'] = true; - } - - return $result; - - } - - - /** - * Returns information about Copy and Move requests - * - * This function is created to help getting information about the source and the destination for the - * WebDAV MOVE and COPY HTTP request. It also validates a lot of information and throws proper exceptions - * - * The returned value is an array with the following keys: - * * destination - Destination path - * * destinationExists - Whether or not the destination is an existing url (and should therefore be overwritten) - * - * @return array - */ - public function getCopyAndMoveInfo() { - - // Collecting the relevant HTTP headers - if (!$this->httpRequest->getHeader('Destination')) throw new Exception\BadRequest('The destination header was not supplied'); - $destination = $this->calculateUri($this->httpRequest->getHeader('Destination')); - $overwrite = $this->httpRequest->getHeader('Overwrite'); - if (!$overwrite) $overwrite = 'T'; - if (strtoupper($overwrite)=='T') $overwrite = true; - elseif (strtoupper($overwrite)=='F') $overwrite = false; - // We need to throw a bad request exception, if the header was invalid - else throw new Exception\BadRequest('The HTTP Overwrite header should be either T or F'); - - list($destinationDir) = URLUtil::splitPath($destination); - - try { - $destinationParent = $this->tree->getNodeForPath($destinationDir); - if (!($destinationParent instanceof ICollection)) throw new Exception\UnsupportedMediaType('The destination node is not a collection'); - } catch (Exception\NotFound $e) { - - // If the destination parent node is not found, we throw a 409 - throw new Exception\Conflict('The destination node is not found'); - } - - try { - - $destinationNode = $this->tree->getNodeForPath($destination); - - // If this succeeded, it means the destination already exists - // we'll need to throw precondition failed in case overwrite is false - if (!$overwrite) throw new Exception\PreconditionFailed('The destination node already exists, and the overwrite header is set to false','Overwrite'); - - } catch (Exception\NotFound $e) { - - // Destination didn't exist, we're all good - $destinationNode = false; - - - - } - - // These are the three relevant properties we need to return - return array( - 'destination' => $destination, - 'destinationExists' => $destinationNode==true, - 'destinationNode' => $destinationNode, - ); - - } - - /** - * Returns a list of properties for a path - * - * This is a simplified version getPropertiesForPath. - * if you aren't interested in status codes, but you just - * want to have a flat list of properties. Use this method. - * - * @param string $path - * @param array $propertyNames - */ - public function getProperties($path, $propertyNames) { - - $result = $this->getPropertiesForPath($path,$propertyNames,0); - return $result[0][200]; - - } - - /** - * A kid-friendly way to fetch properties for a node's children. - * - * The returned array will be indexed by the path of the of child node. - * Only properties that are actually found will be returned. - * - * The parent node will not be returned. - * - * @param string $path - * @param array $propertyNames - * @return array - */ - public function getPropertiesForChildren($path, $propertyNames) { - - $result = array(); - foreach($this->getPropertiesForPath($path,$propertyNames,1) as $k=>$row) { - - // Skipping the parent path - if ($k === 0) continue; - - $result[$row['href']] = $row[200]; - - } - return $result; - - } - - /** - * Returns a list of HTTP headers for a particular resource - * - * The generated http headers are based on properties provided by the - * resource. The method basically provides a simple mapping between - * DAV property and HTTP header. - * - * The headers are intended to be used for HEAD and GET requests. - * - * @param string $path - * @return array - */ - public function getHTTPHeaders($path) { - - $propertyMap = array( - '{DAV:}getcontenttype' => 'Content-Type', - '{DAV:}getcontentlength' => 'Content-Length', - '{DAV:}getlastmodified' => 'Last-Modified', - '{DAV:}getetag' => 'ETag', - ); - - $properties = $this->getProperties($path,array_keys($propertyMap)); - - $headers = array(); - foreach($propertyMap as $property=>$header) { - if (!isset($properties[$property])) continue; - - if (is_scalar($properties[$property])) { - $headers[$header] = $properties[$property]; - - // GetLastModified gets special cased - } elseif ($properties[$property] instanceof Property\GetLastModified) { - $headers[$header] = HTTP\Util::toHTTPDate($properties[$property]->getTime()); - } - - } - - return $headers; - - } - - /** - * Returns a list of properties for a given path - * - * The path that should be supplied should have the baseUrl stripped out - * The list of properties should be supplied in Clark notation. If the list is empty - * 'allprops' is assumed. - * - * If a depth of 1 is requested child elements will also be returned. - * - * @param string $path - * @param array $propertyNames - * @param int $depth - * @return array - */ - public function getPropertiesForPath($path, $propertyNames = array(), $depth = 0) { - - if ($depth!=0) $depth = 1; - - $path = rtrim($path,'/'); - - // This event allows people to intercept these requests early on in the - // process. - // - // We're not doing anything with the result, but this can be helpful to - // pre-fetch certain expensive live properties. - $this->broadCastEvent('beforeGetPropertiesForPath', array($path, $propertyNames, $depth)); - - $returnPropertyList = array(); - - $parentNode = $this->tree->getNodeForPath($path); - $nodes = array( - $path => $parentNode - ); - if ($depth==1 && $parentNode instanceof ICollection) { - foreach($this->tree->getChildren($path) as $childNode) - $nodes[$path . '/' . $childNode->getName()] = $childNode; - } - - // If the propertyNames array is empty, it means all properties are requested. - // We shouldn't actually return everything we know though, and only return a - // sensible list. - $allProperties = count($propertyNames)==0; - - foreach($nodes as $myPath=>$node) { - - $currentPropertyNames = $propertyNames; - - $newProperties = array( - '200' => array(), - '404' => array(), - ); - - if ($allProperties) { - // Default list of propertyNames, when all properties were requested. - $currentPropertyNames = array( - '{DAV:}getlastmodified', - '{DAV:}getcontentlength', - '{DAV:}resourcetype', - '{DAV:}quota-used-bytes', - '{DAV:}quota-available-bytes', - '{DAV:}getetag', - '{DAV:}getcontenttype', - ); - } - - // If the resourceType was not part of the list, we manually add it - // and mark it for removal. We need to know the resourcetype in order - // to make certain decisions about the entry. - // WebDAV dictates we should add a / and the end of href's for collections - $removeRT = false; - if (!in_array('{DAV:}resourcetype',$currentPropertyNames)) { - $currentPropertyNames[] = '{DAV:}resourcetype'; - $removeRT = true; - } - - $result = $this->broadcastEvent('beforeGetProperties',array($myPath, $node, &$currentPropertyNames, &$newProperties)); - // If this method explicitly returned false, we must ignore this - // node as it is inaccessible. - if ($result===false) continue; - - if (count($currentPropertyNames) > 0) { - - if ($node instanceof IProperties) { - $nodeProperties = $node->getProperties($currentPropertyNames); - - // The getProperties method may give us too much, - // properties, in case the implementor was lazy. - // - // So as we loop through this list, we will only take the - // properties that were actually requested and discard the - // rest. - foreach($currentPropertyNames as $k=>$currentPropertyName) { - if (isset($nodeProperties[$currentPropertyName])) { - unset($currentPropertyNames[$k]); - $newProperties[200][$currentPropertyName] = $nodeProperties[$currentPropertyName]; - } - } - - } - - } - - foreach($currentPropertyNames as $prop) { - - if (isset($newProperties[200][$prop])) continue; - - switch($prop) { - case '{DAV:}getlastmodified' : if ($node->getLastModified()) $newProperties[200][$prop] = new Property\GetLastModified($node->getLastModified()); break; - case '{DAV:}getcontentlength' : - if ($node instanceof IFile) { - $size = $node->getSize(); - if (!is_null($size)) { - $newProperties[200][$prop] = (int)$node->getSize(); - } - } - break; - case '{DAV:}quota-used-bytes' : - if ($node instanceof IQuota) { - $quotaInfo = $node->getQuotaInfo(); - $newProperties[200][$prop] = $quotaInfo[0]; - } - break; - case '{DAV:}quota-available-bytes' : - if ($node instanceof IQuota) { - $quotaInfo = $node->getQuotaInfo(); - $newProperties[200][$prop] = $quotaInfo[1]; - } - break; - case '{DAV:}getetag' : if ($node instanceof IFile && $etag = $node->getETag()) $newProperties[200][$prop] = $etag; break; - case '{DAV:}getcontenttype' : if ($node instanceof IFile && $ct = $node->getContentType()) $newProperties[200][$prop] = $ct; break; - case '{DAV:}supported-report-set' : - $reports = array(); - foreach($this->plugins as $plugin) { - $reports = array_merge($reports, $plugin->getSupportedReportSet($myPath)); - } - $newProperties[200][$prop] = new Property\SupportedReportSet($reports); - break; - case '{DAV:}resourcetype' : - $newProperties[200]['{DAV:}resourcetype'] = new Property\ResourceType(); - foreach($this->resourceTypeMapping as $className => $resourceType) { - if ($node instanceof $className) $newProperties[200]['{DAV:}resourcetype']->add($resourceType); - } - break; - - } - - // If we were unable to find the property, we will list it as 404. - if (!$allProperties && !isset($newProperties[200][$prop])) $newProperties[404][$prop] = null; - - } - - $this->broadcastEvent('afterGetProperties',array(trim($myPath,'/'),&$newProperties, $node)); - - $newProperties['href'] = trim($myPath,'/'); - - // Its is a WebDAV recommendation to add a trailing slash to collectionnames. - // Apple's iCal also requires a trailing slash for principals (rfc 3744), though this is non-standard. - if ($myPath!='' && isset($newProperties[200]['{DAV:}resourcetype'])) { - $rt = $newProperties[200]['{DAV:}resourcetype']; - if ($rt->is('{DAV:}collection') || $rt->is('{DAV:}principal')) { - $newProperties['href'] .='/'; - } - } - - // If the resourcetype property was manually added to the requested property list, - // we will remove it again. - if ($removeRT) unset($newProperties[200]['{DAV:}resourcetype']); - - $returnPropertyList[] = $newProperties; - - } - - return $returnPropertyList; - - } - - /** - * This method is invoked by sub-systems creating a new file. - * - * Currently this is done by HTTP PUT and HTTP LOCK (in the Locks_Plugin). - * It was important to get this done through a centralized function, - * allowing plugins to intercept this using the beforeCreateFile event. - * - * This method will return true if the file was actually created - * - * @param string $uri - * @param resource $data - * @param string $etag - * @return bool - */ - public function createFile($uri,$data, &$etag = null) { - - list($dir,$name) = URLUtil::splitPath($uri); - - if (!$this->broadcastEvent('beforeBind',array($uri))) return false; - - $parent = $this->tree->getNodeForPath($dir); - if (!$parent instanceof ICollection) { - throw new Exception\Conflict('Files can only be created as children of collections'); - } - - if (!$this->broadcastEvent('beforeCreateFile',array($uri, &$data, $parent))) return false; - - $etag = $parent->createFile($name,$data); - $this->tree->markDirty($dir . '/' . $name); - - $this->broadcastEvent('afterBind',array($uri)); - $this->broadcastEvent('afterCreateFile',array($uri, $parent)); - - return true; - } - - /** - * This method is invoked by sub-systems creating a new directory. - * - * @param string $uri - * @return void - */ - public function createDirectory($uri) { - - $this->createCollection($uri,array('{DAV:}collection'),array()); - - } - - /** - * Use this method to create a new collection - * - * The {DAV:}resourcetype is specified using the resourceType array. - * At the very least it must contain {DAV:}collection. - * - * The properties array can contain a list of additional properties. - * - * @param string $uri The new uri - * @param array $resourceType The resourceType(s) - * @param array $properties A list of properties - * @return array|null - */ - public function createCollection($uri, array $resourceType, array $properties) { - - list($parentUri,$newName) = URLUtil::splitPath($uri); - - // Making sure {DAV:}collection was specified as resourceType - if (!in_array('{DAV:}collection', $resourceType)) { - throw new Exception\InvalidResourceType('The resourceType for this collection must at least include {DAV:}collection'); - } - - - // Making sure the parent exists - try { - - $parent = $this->tree->getNodeForPath($parentUri); - - } catch (Exception\NotFound $e) { - - throw new Exception\Conflict('Parent node does not exist'); - - } - - // Making sure the parent is a collection - if (!$parent instanceof ICollection) { - throw new Exception\Conflict('Parent node is not a collection'); - } - - - - // Making sure the child does not already exist - try { - $parent->getChild($newName); - - // If we got here.. it means there's already a node on that url, and we need to throw a 405 - throw new Exception\MethodNotAllowed('The resource you tried to create already exists'); - - } catch (Exception\NotFound $e) { - // This is correct - } - - - if (!$this->broadcastEvent('beforeBind',array($uri))) return; - - // There are 2 modes of operation. The standard collection - // creates the directory, and then updates properties - // the extended collection can create it directly. - if ($parent instanceof IExtendedCollection) { - - $parent->createExtendedCollection($newName, $resourceType, $properties); - - } else { - - // No special resourcetypes are supported - if (count($resourceType)>1) { - throw new Exception\InvalidResourceType('The {DAV:}resourcetype you specified is not supported here.'); - } - - $parent->createDirectory($newName); - $rollBack = false; - $exception = null; - $errorResult = null; - - if (count($properties)>0) { - - try { - - $errorResult = $this->updateProperties($uri, $properties); - if (!isset($errorResult[200])) { - $rollBack = true; - } - - } catch (Exception $e) { - - $rollBack = true; - $exception = $e; - - } - - } - - if ($rollBack) { - if (!$this->broadcastEvent('beforeUnbind',array($uri))) return; - $this->tree->delete($uri); - - // Re-throwing exception - if ($exception) throw $exception; - - return $errorResult; - } - - } - $this->tree->markDirty($parentUri); - $this->broadcastEvent('afterBind',array($uri)); - - } - - /** - * This method updates a resource's properties - * - * The properties array must be a list of properties. Array-keys are - * property names in clarknotation, array-values are it's values. - * If a property must be deleted, the value should be null. - * - * Note that this request should either completely succeed, or - * completely fail. - * - * The response is an array with statuscodes for keys, which in turn - * contain arrays with propertynames. This response can be used - * to generate a multistatus body. - * - * @param string $uri - * @param array $properties - * @return array - */ - public function updateProperties($uri, array $properties) { - - // we'll start by grabbing the node, this will throw the appropriate - // exceptions if it doesn't. - $node = $this->tree->getNodeForPath($uri); - - $result = array( - 200 => array(), - 403 => array(), - 424 => array(), - ); - $remainingProperties = $properties; - $hasError = false; - - // Running through all properties to make sure none of them are protected - if (!$hasError) foreach($properties as $propertyName => $value) { - if(in_array($propertyName, $this->protectedProperties)) { - $result[403][$propertyName] = null; - unset($remainingProperties[$propertyName]); - $hasError = true; - } - } - - if (!$hasError) { - // Allowing plugins to take care of property updating - $hasError = !$this->broadcastEvent('updateProperties',array( - &$remainingProperties, - &$result, - $node - )); - } - - // If the node is not an instance of Sabre\DAV\IProperties, every - // property is 403 Forbidden - if (!$hasError && count($remainingProperties) && !($node instanceof IProperties)) { - $hasError = true; - foreach($properties as $propertyName=> $value) { - $result[403][$propertyName] = null; - } - $remainingProperties = array(); - } - - // Only if there were no errors we may attempt to update the resource - if (!$hasError) { - - if (count($remainingProperties)>0) { - - $updateResult = $node->updateProperties($remainingProperties); - - if ($updateResult===true) { - // success - foreach($remainingProperties as $propertyName=>$value) { - $result[200][$propertyName] = null; - } - - } elseif ($updateResult===false) { - // The node failed to update the properties for an - // unknown reason - foreach($remainingProperties as $propertyName=>$value) { - $result[403][$propertyName] = null; - } - - } elseif (is_array($updateResult)) { - - // The node has detailed update information - // We need to merge the results with the earlier results. - foreach($updateResult as $status => $props) { - if (is_array($props)) { - if (!isset($result[$status])) - $result[$status] = array(); - - $result[$status] = array_merge($result[$status], $updateResult[$status]); - } - } - - } else { - throw new Exception('Invalid result from updateProperties'); - } - $remainingProperties = array(); - } - - } - - foreach($remainingProperties as $propertyName=>$value) { - // if there are remaining properties, it must mean - // there's a dependency failure - $result[424][$propertyName] = null; - } - - // Removing empty array values - foreach($result as $status=>$props) { - - if (count($props)===0) unset($result[$status]); - - } - $result['href'] = $uri; - return $result; - - } - - /** - * This method checks the main HTTP preconditions. - * - * Currently these are: - * * If-Match - * * If-None-Match - * * If-Modified-Since - * * If-Unmodified-Since - * - * The method will return true if all preconditions are met - * The method will return false, or throw an exception if preconditions - * failed. If false is returned the operation should be aborted, and - * the appropriate HTTP response headers are already set. - * - * Normally this method will throw 412 Precondition Failed for failures - * related to If-None-Match, If-Match and If-Unmodified Since. It will - * set the status to 304 Not Modified for If-Modified_since. - * - * If the $handleAsGET argument is set to true, it will also return 304 - * Not Modified for failure of the If-None-Match precondition. This is the - * desired behaviour for HTTP GET and HTTP HEAD requests. - * - * @param bool $handleAsGET - * @return bool - */ - public function checkPreconditions($handleAsGET = false) { - - $uri = $this->getRequestUri(); - $node = null; - $lastMod = null; - $etag = null; - - if ($ifMatch = $this->httpRequest->getHeader('If-Match')) { - - // If-Match contains an entity tag. Only if the entity-tag - // matches we are allowed to make the request succeed. - // If the entity-tag is '*' we are only allowed to make the - // request succeed if a resource exists at that url. - try { - $node = $this->tree->getNodeForPath($uri); - } catch (Exception\NotFound $e) { - throw new Exception\PreconditionFailed('An If-Match header was specified and the resource did not exist','If-Match'); - } - - // Only need to check entity tags if they are not * - if ($ifMatch!=='*') { - - // There can be multiple etags - $ifMatch = explode(',',$ifMatch); - $haveMatch = false; - foreach($ifMatch as $ifMatchItem) { - - // Stripping any extra spaces - $ifMatchItem = trim($ifMatchItem,' '); - - $etag = $node->getETag(); - if ($etag===$ifMatchItem) { - $haveMatch = true; - } else { - // Evolution has a bug where it sometimes prepends the " - // with a \. This is our workaround. - if (str_replace('\\"','"', $ifMatchItem) === $etag) { - $haveMatch = true; - } - } - - } - if (!$haveMatch) { - throw new Exception\PreconditionFailed('An If-Match header was specified, but none of the specified the ETags matched.','If-Match'); - } - } - } - - if ($ifNoneMatch = $this->httpRequest->getHeader('If-None-Match')) { - - // The If-None-Match header contains an etag. - // Only if the ETag does not match the current ETag, the request will succeed - // The header can also contain *, in which case the request - // will only succeed if the entity does not exist at all. - $nodeExists = true; - if (!$node) { - try { - $node = $this->tree->getNodeForPath($uri); - } catch (Exception\NotFound $e) { - $nodeExists = false; - } - } - if ($nodeExists) { - $haveMatch = false; - if ($ifNoneMatch==='*') $haveMatch = true; - else { - - // There might be multiple etags - $ifNoneMatch = explode(',', $ifNoneMatch); - $etag = $node->getETag(); - - foreach($ifNoneMatch as $ifNoneMatchItem) { - - // Stripping any extra spaces - $ifNoneMatchItem = trim($ifNoneMatchItem,' '); - - if ($etag===$ifNoneMatchItem) $haveMatch = true; - - } - - } - - if ($haveMatch) { - if ($handleAsGET) { - $this->httpResponse->sendStatus(304); - return false; - } else { - throw new Exception\PreconditionFailed('An If-None-Match header was specified, but the ETag matched (or * was specified).','If-None-Match'); - } - } - } - - } - - if (!$ifNoneMatch && ($ifModifiedSince = $this->httpRequest->getHeader('If-Modified-Since'))) { - - // The If-Modified-Since header contains a date. We - // will only return the entity if it has been changed since - // that date. If it hasn't been changed, we return a 304 - // header - // Note that this header only has to be checked if there was no If-None-Match header - // as per the HTTP spec. - $date = HTTP\Util::parseHTTPDate($ifModifiedSince); - - if ($date) { - if (is_null($node)) { - $node = $this->tree->getNodeForPath($uri); - } - $lastMod = $node->getLastModified(); - if ($lastMod) { - $lastMod = new \DateTime('@' . $lastMod); - if ($lastMod <= $date) { - $this->httpResponse->sendStatus(304); - $this->httpResponse->setHeader('Last-Modified', HTTP\Util::toHTTPDate($lastMod)); - return false; - } - } - } - } - - if ($ifUnmodifiedSince = $this->httpRequest->getHeader('If-Unmodified-Since')) { - - // The If-Unmodified-Since will allow allow the request if the - // entity has not changed since the specified date. - $date = HTTP\Util::parseHTTPDate($ifUnmodifiedSince); - - // We must only check the date if it's valid - if ($date) { - if (is_null($node)) { - $node = $this->tree->getNodeForPath($uri); - } - $lastMod = $node->getLastModified(); - if ($lastMod) { - $lastMod = new \DateTime('@' . $lastMod); - if ($lastMod > $date) { - throw new Exception\PreconditionFailed('An If-Unmodified-Since header was specified, but the entity has been changed since the specified date.','If-Unmodified-Since'); - } - } - } - - } - return true; - - } - - // }}} - // {{{ XML Readers & Writers - - - /** - * Generates a WebDAV propfind response body based on a list of nodes. - * - * If 'strip404s' is set to true, all 404 responses will be removed. - * - * @param array $fileProperties The list with nodes - * @param bool strip404s - * @return string - */ - public function generateMultiStatus(array $fileProperties, $strip404s = false) { - - $dom = new \DOMDocument('1.0','utf-8'); - //$dom->formatOutput = true; - $multiStatus = $dom->createElement('d:multistatus'); - $dom->appendChild($multiStatus); - - // Adding in default namespaces - foreach($this->xmlNamespaces as $namespace=>$prefix) { - - $multiStatus->setAttribute('xmlns:' . $prefix,$namespace); - - } - - foreach($fileProperties as $entry) { - - $href = $entry['href']; - unset($entry['href']); - - if ($strip404s && isset($entry[404])) { - unset($entry[404]); - } - - $response = new Property\Response($href,$entry); - $response->serialize($this,$multiStatus); - - } - - return $dom->saveXML(); - - } - - /** - * This method parses a PropPatch request - * - * PropPatch changes the properties for a resource. This method - * returns a list of properties. - * - * The keys in the returned array contain the property name (e.g.: {DAV:}displayname, - * and the value contains the property value. If a property is to be removed the value - * will be null. - * - * @param string $body xml body - * @return array list of properties in need of updating or deletion - */ - public function parsePropPatchRequest($body) { - - //We'll need to change the DAV namespace declaration to something else in order to make it parsable - $dom = XMLUtil::loadDOMDocument($body); - - $newProperties = array(); - - foreach($dom->firstChild->childNodes as $child) { - - if ($child->nodeType !== XML_ELEMENT_NODE) continue; - - $operation = XMLUtil::toClarkNotation($child); - - if ($operation!=='{DAV:}set' && $operation!=='{DAV:}remove') continue; - - $innerProperties = XMLUtil::parseProperties($child, $this->propertyMap); - - foreach($innerProperties as $propertyName=>$propertyValue) { - - if ($operation==='{DAV:}remove') { - $propertyValue = null; - } - - $newProperties[$propertyName] = $propertyValue; - - } - - } - - return $newProperties; - - } - - /** - * This method parses the PROPFIND request and returns its information - * - * This will either be a list of properties, or an empty array; in which case - * an {DAV:}allprop was requested. - * - * @param string $body - * @return array - */ - public function parsePropFindRequest($body) { - - // If the propfind body was empty, it means IE is requesting 'all' properties - if (!$body) return array(); - - $dom = XMLUtil::loadDOMDocument($body); - $elem = $dom->getElementsByTagNameNS('urn:DAV','propfind')->item(0); - return array_keys(XMLUtil::parseProperties($elem)); - - } - - // }}} - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAV/ServerPlugin.php b/vendor/sabre/dav/lib/Sabre/DAV/ServerPlugin.php deleted file mode 100644 index c393f43fb..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/ServerPlugin.php +++ /dev/null @@ -1,90 +0,0 @@ -name = $name; - foreach($children as $child) { - - if (!($child instanceof INode)) throw new Exception('Only instances of Sabre\DAV\INode are allowed to be passed in the children argument'); - $this->addChild($child); - - } - - } - - /** - * Adds a new childnode to this collection - * - * @param INode $child - * @return void - */ - public function addChild(INode $child) { - - $this->children[$child->getName()] = $child; - - } - - /** - * Returns the name of the collection - * - * @return string - */ - public function getName() { - - return $this->name; - - } - - /** - * Returns a child object, by its name. - * - * This method makes use of the getChildren method to grab all the child nodes, and compares the name. - * Generally its wise to override this, as this can usually be optimized - * - * This method must throw Sabre\DAV\Exception\NotFound if the node does not - * exist. - * - * @param string $name - * @throws Exception\NotFound - * @return INode - */ - public function getChild($name) { - - if (isset($this->children[$name])) return $this->children[$name]; - throw new Exception\NotFound('File not found: ' . $name . ' in \'' . $this->getName() . '\''); - - } - - /** - * Returns a list of children for this collection - * - * @return array - */ - public function getChildren() { - - return array_values($this->children); - - } - - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAV/SimpleFile.php b/vendor/sabre/dav/lib/Sabre/DAV/SimpleFile.php deleted file mode 100644 index b7413fdde..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/SimpleFile.php +++ /dev/null @@ -1,121 +0,0 @@ -name = $name; - $this->contents = $contents; - $this->mimeType = $mimeType; - - } - - /** - * Returns the node name for this file. - * - * This name is used to construct the url. - * - * @return string - */ - public function getName() { - - return $this->name; - - } - - /** - * Returns the data - * - * This method may either return a string or a readable stream resource - * - * @return mixed - */ - public function get() { - - return $this->contents; - - } - - /** - * Returns the size of the file, in bytes. - * - * @return int - */ - public function getSize() { - - return strlen($this->contents); - - } - - /** - * Returns the ETag for a file - * - * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. - * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. - * - * Return null if the ETag can not effectively be determined - * @return string - */ - public function getETag() { - - return '"' . md5($this->contents) . '"'; - - } - - /** - * Returns the mime-type for a file - * - * If null is returned, we'll assume application/octet-stream - * @return string - */ - public function getContentType() { - - return $this->mimeType; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/StringUtil.php b/vendor/sabre/dav/lib/Sabre/DAV/StringUtil.php deleted file mode 100644 index c71575f49..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/StringUtil.php +++ /dev/null @@ -1,91 +0,0 @@ -dataDir = $dataDir; - - } - - /** - * Initialize the plugin - * - * This is called automatically be the Server class after this plugin is - * added with Sabre\DAV\Server::addPlugin() - * - * @param Server $server - * @return void - */ - public function initialize(Server $server) { - - $this->server = $server; - $server->subscribeEvent('beforeMethod',array($this,'beforeMethod')); - $server->subscribeEvent('beforeCreateFile',array($this,'beforeCreateFile')); - - } - - /** - * This method is called before any HTTP method handler - * - * This method intercepts any GET, DELETE, PUT and PROPFIND calls to - * filenames that are known to match the 'temporary file' regex. - * - * @param string $method - * @param string $uri - * @return bool - */ - public function beforeMethod($method, $uri) { - - if (!$tempLocation = $this->isTempFile($uri)) - return true; - - switch($method) { - case 'GET' : - return $this->httpGet($tempLocation); - case 'PUT' : - return $this->httpPut($tempLocation); - case 'PROPFIND' : - return $this->httpPropfind($tempLocation, $uri); - case 'DELETE' : - return $this->httpDelete($tempLocation); - } - return true; - - } - - /** - * This method is invoked if some subsystem creates a new file. - * - * This is used to deal with HTTP LOCK requests which create a new - * file. - * - * @param string $uri - * @param resource $data - * @return bool - */ - public function beforeCreateFile($uri,$data) { - - if ($tempPath = $this->isTempFile($uri)) { - - $hR = $this->server->httpResponse; - $hR->setHeader('X-Sabre-Temp','true'); - file_put_contents($tempPath,$data); - return false; - } - return true; - - } - - /** - * This method will check if the url matches the temporary file pattern - * if it does, it will return an path based on $this->dataDir for the - * temporary file storage. - * - * @param string $path - * @return boolean|string - */ - protected function isTempFile($path) { - - // We're only interested in the basename. - list(, $tempPath) = URLUtil::splitPath($path); - - foreach($this->temporaryFilePatterns as $tempFile) { - - if (preg_match($tempFile,$tempPath)) { - return $this->getDataDir() . '/sabredav_' . md5($path) . '.tempfile'; - } - - } - - return false; - - } - - - /** - * This method handles the GET method for temporary files. - * If the file doesn't exist, it will return false which will kick in - * the regular system for the GET method. - * - * @param string $tempLocation - * @return bool - */ - public function httpGet($tempLocation) { - - if (!file_exists($tempLocation)) return true; - - $hR = $this->server->httpResponse; - $hR->setHeader('Content-Type','application/octet-stream'); - $hR->setHeader('Content-Length',filesize($tempLocation)); - $hR->setHeader('X-Sabre-Temp','true'); - $hR->sendStatus(200); - $hR->sendBody(fopen($tempLocation,'r')); - return false; - - } - - /** - * This method handles the PUT method. - * - * @param string $tempLocation - * @return bool - */ - public function httpPut($tempLocation) { - - $hR = $this->server->httpResponse; - $hR->setHeader('X-Sabre-Temp','true'); - - $newFile = !file_exists($tempLocation); - - if (!$newFile && ($this->server->httpRequest->getHeader('If-None-Match'))) { - throw new Exception\PreconditionFailed('The resource already exists, and an If-None-Match header was supplied'); - } - - file_put_contents($tempLocation,$this->server->httpRequest->getBody()); - $hR->sendStatus($newFile?201:200); - return false; - - } - - /** - * This method handles the DELETE method. - * - * If the file didn't exist, it will return false, which will make the - * standard HTTP DELETE handler kick in. - * - * @param string $tempLocation - * @return bool - */ - public function httpDelete($tempLocation) { - - if (!file_exists($tempLocation)) return true; - - unlink($tempLocation); - $hR = $this->server->httpResponse; - $hR->setHeader('X-Sabre-Temp','true'); - $hR->sendStatus(204); - return false; - - } - - /** - * This method handles the PROPFIND method. - * - * It's a very lazy method, it won't bother checking the request body - * for which properties were requested, and just sends back a default - * set of properties. - * - * @param string $tempLocation - * @param string $uri - * @return bool - */ - public function httpPropfind($tempLocation, $uri) { - - if (!file_exists($tempLocation)) return true; - - $hR = $this->server->httpResponse; - $hR->setHeader('X-Sabre-Temp','true'); - $hR->sendStatus(207); - $hR->setHeader('Content-Type','application/xml; charset=utf-8'); - - $this->server->parsePropFindRequest($this->server->httpRequest->getBody(true)); - - $properties = array( - 'href' => $uri, - 200 => array( - '{DAV:}getlastmodified' => new Property\GetLastModified(filemtime($tempLocation)), - '{DAV:}getcontentlength' => filesize($tempLocation), - '{DAV:}resourcetype' => new Property\ResourceType(null), - '{'.Server::NS_SABREDAV.'}tempFile' => true, - - ), - ); - - $data = $this->server->generateMultiStatus(array($properties)); - $hR->sendBody($data); - return false; - - } - - - /** - * This method returns the directory where the temporary files should be stored. - * - * @return string - */ - protected function getDataDir() - { - return $this->dataDir; - } -} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Tree.php b/vendor/sabre/dav/lib/Sabre/DAV/Tree.php deleted file mode 100644 index ab94168b2..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Tree.php +++ /dev/null @@ -1,193 +0,0 @@ -getNodeForPath($path); - return true; - - } catch (Exception\NotFound $e) { - - return false; - - } - - } - - /** - * Copies a file from path to another - * - * @param string $sourcePath The source location - * @param string $destinationPath The full destination path - * @return void - */ - public function copy($sourcePath, $destinationPath) { - - $sourceNode = $this->getNodeForPath($sourcePath); - - // grab the dirname and basename components - list($destinationDir, $destinationName) = URLUtil::splitPath($destinationPath); - - $destinationParent = $this->getNodeForPath($destinationDir); - $this->copyNode($sourceNode,$destinationParent,$destinationName); - - $this->markDirty($destinationDir); - - } - - /** - * Moves a file from one location to another - * - * @param string $sourcePath The path to the file which should be moved - * @param string $destinationPath The full destination path, so not just the destination parent node - * @return int - */ - public function move($sourcePath, $destinationPath) { - - list($sourceDir, $sourceName) = URLUtil::splitPath($sourcePath); - list($destinationDir, $destinationName) = URLUtil::splitPath($destinationPath); - - if ($sourceDir===$destinationDir) { - $renameable = $this->getNodeForPath($sourcePath); - $renameable->setName($destinationName); - } else { - $this->copy($sourcePath,$destinationPath); - $this->getNodeForPath($sourcePath)->delete(); - } - $this->markDirty($sourceDir); - $this->markDirty($destinationDir); - - } - - /** - * Deletes a node from the tree - * - * @param string $path - * @return void - */ - public function delete($path) { - - $node = $this->getNodeForPath($path); - $node->delete(); - - list($parent) = URLUtil::splitPath($path); - $this->markDirty($parent); - - } - - /** - * Returns a list of childnodes for a given path. - * - * @param string $path - * @return array - */ - public function getChildren($path) { - - $node = $this->getNodeForPath($path); - return $node->getChildren(); - - } - - /** - * This method is called with every tree update - * - * Examples of tree updates are: - * * node deletions - * * node creations - * * copy - * * move - * * renaming nodes - * - * If Tree classes implement a form of caching, this will allow - * them to make sure caches will be expired. - * - * If a path is passed, it is assumed that the entire subtree is dirty - * - * @param string $path - * @return void - */ - public function markDirty($path) { - - - } - - /** - * copyNode - * - * @param INode $source - * @param ICollection $destinationParent - * @param string $destinationName - * @return void - */ - protected function copyNode(INode $source,ICollection $destinationParent,$destinationName = null) { - - if (!$destinationName) $destinationName = $source->getName(); - - if ($source instanceof IFile) { - - $data = $source->get(); - - // If the body was a string, we need to convert it to a stream - if (is_string($data)) { - $stream = fopen('php://temp','r+'); - fwrite($stream,$data); - rewind($stream); - $data = $stream; - } - $destinationParent->createFile($destinationName,$data); - $destination = $destinationParent->getChild($destinationName); - - } elseif ($source instanceof ICollection) { - - $destinationParent->createDirectory($destinationName); - - $destination = $destinationParent->getChild($destinationName); - foreach($source->getChildren() as $child) { - - $this->copyNode($child,$destination); - - } - - } - if ($source instanceof IProperties && $destination instanceof IProperties) { - - $props = $source->getProperties(array()); - $destination->updateProperties($props); - - } - - } - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Tree/Filesystem.php b/vendor/sabre/dav/lib/Sabre/DAV/Tree/Filesystem.php deleted file mode 100644 index a477725a5..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/Tree/Filesystem.php +++ /dev/null @@ -1,133 +0,0 @@ -basePath = $basePath; - - } - - /** - * Returns a new node for the given path - * - * @param string $path - * @return DAV\FS\Node - */ - public function getNodeForPath($path) { - - $realPath = $this->getRealPath($path); - if (!file_exists($realPath)) { - throw new DAV\Exception\NotFound('File at location ' . $realPath . ' not found'); - } - if (is_dir($realPath)) { - return new DAV\FS\Directory($realPath); - } else { - return new DAV\FS\File($realPath); - } - - } - - /** - * Returns the real filesystem path for a webdav url. - * - * @param string $publicPath - * @return string - */ - protected function getRealPath($publicPath) { - - return rtrim($this->basePath,'/') . '/' . trim($publicPath,'/'); - - } - - /** - * Copies a file or directory. - * - * This method must work recursively and delete the destination - * if it exists - * - * @param string $source - * @param string $destination - * @return void - */ - public function copy($source,$destination) { - - $source = $this->getRealPath($source); - $destination = $this->getRealPath($destination); - $this->realCopy($source,$destination); - - } - - /** - * Used by self::copy - * - * @param string $source - * @param string $destination - * @return void - */ - protected function realCopy($source,$destination) { - - if (is_file($source)) { - copy($source,$destination); - } else { - mkdir($destination); - foreach(scandir($source) as $subnode) { - - if ($subnode=='.' || $subnode=='..') continue; - $this->realCopy($source.'/'.$subnode,$destination.'/'.$subnode); - - } - } - - } - - /** - * Moves a file or directory recursively. - * - * If the destination exists, delete it first. - * - * @param string $source - * @param string $destination - * @return void - */ - public function move($source,$destination) { - - $source = $this->getRealPath($source); - $destination = $this->getRealPath($destination); - rename($source,$destination); - - } - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAV/URLUtil.php b/vendor/sabre/dav/lib/Sabre/DAV/URLUtil.php deleted file mode 100644 index b7254e9a1..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAV/URLUtil.php +++ /dev/null @@ -1,124 +0,0 @@ - - * will be returned as: - * {http://www.example.org}myelem - * - * This format is used throughout the SabreDAV sourcecode. - * Elements encoded with the urn:DAV namespace will - * be returned as if they were in the DAV: namespace. This is to avoid - * compatibility problems. - * - * This function will return null if a nodetype other than an Element is passed. - * - * @param \DOMNode $dom - * @return string - */ - static function toClarkNotation(\DOMNode $dom) { - - if ($dom->nodeType !== XML_ELEMENT_NODE) return null; - - // Mapping back to the real namespace, in case it was dav - if ($dom->namespaceURI=='urn:DAV') $ns = 'DAV:'; else $ns = $dom->namespaceURI; - - // Mapping to clark notation - return '{' . $ns . '}' . $dom->localName; - - } - - /** - * Parses a clark-notation string, and returns the namespace and element - * name components. - * - * If the string was invalid, it will throw an InvalidArgumentException. - * - * @param string $str - * @throws InvalidArgumentException - * @return array - */ - static function parseClarkNotation($str) { - - if (!preg_match('/^{([^}]*)}(.*)$/',$str,$matches)) { - throw new \InvalidArgumentException('\'' . $str . '\' is not a valid clark-notation formatted string'); - } - - return array( - $matches[1], - $matches[2] - ); - - } - - /** - * This method takes an XML document (as string) and converts all instances of the - * DAV: namespace to urn:DAV - * - * This is unfortunately needed, because the DAV: namespace violates the xml namespaces - * spec, and causes the DOM to throw errors - * - * @param string $xmlDocument - * @return array|string|null - */ - static function convertDAVNamespace($xmlDocument) { - - // This is used to map the DAV: namespace to urn:DAV. This is needed, because the DAV: - // namespace is actually a violation of the XML namespaces specification, and will cause errors - return preg_replace("/xmlns(:[A-Za-z0-9_]*)?=(\"|\')DAV:(\\2)/","xmlns\\1=\\2urn:DAV\\2",$xmlDocument); - - } - - /** - * This method provides a generic way to load a DOMDocument for WebDAV use. - * - * This method throws a Sabre\DAV\Exception\BadRequest exception for any xml errors. - * It does not preserve whitespace, and it converts the DAV: namespace to urn:DAV. - * - * @param string $xml - * @throws Sabre\DAV\Exception\BadRequest - * @return DOMDocument - */ - static function loadDOMDocument($xml) { - - if (empty($xml)) - throw new Exception\BadRequest('Empty XML document sent'); - - // The BitKinex client sends xml documents as UTF-16. PHP 5.3.1 (and presumably lower) - // does not support this, so we must intercept this and convert to UTF-8. - if (substr($xml,0,12) === "\x3c\x00\x3f\x00\x78\x00\x6d\x00\x6c\x00\x20\x00") { - - // Note: the preceeding byte sequence is "]*)encoding="UTF-16"([^>]*)>|u','',$xml); - - } - - // Retaining old error setting - $oldErrorSetting = libxml_use_internal_errors(true); - // Fixes an XXE vulnerability on PHP versions older than 5.3.23 or - // 5.4.13. - $oldEntityLoaderSetting = libxml_disable_entity_loader(true); - - // Clearing any previous errors - libxml_clear_errors(); - - $dom = new \DOMDocument(); - - // We don't generally care about any whitespace - $dom->preserveWhiteSpace = false; - - $dom->loadXML(self::convertDAVNamespace($xml),LIBXML_NOWARNING | LIBXML_NOERROR); - - if ($error = libxml_get_last_error()) { - libxml_clear_errors(); - throw new Exception\BadRequest('The request body had an invalid XML body. (message: ' . $error->message . ', errorcode: ' . $error->code . ', line: ' . $error->line . ')'); - } - - // Restoring old mechanism for error handling - if ($oldErrorSetting===false) libxml_use_internal_errors(false); - if ($oldEntityLoaderSetting===false) libxml_disable_entity_loader(false); - - return $dom; - - } - - /** - * Parses all WebDAV properties out of a DOM Element - * - * Generally WebDAV properties are enclosed in {DAV:}prop elements. This - * method helps by going through all these and pulling out the actual - * propertynames, making them array keys and making the property values, - * well.. the array values. - * - * If no value was given (self-closing element) null will be used as the - * value. This is used in for example PROPFIND requests. - * - * Complex values are supported through the propertyMap argument. The - * propertyMap should have the clark-notation properties as it's keys, and - * classnames as values. - * - * When any of these properties are found, the unserialize() method will be - * (statically) called. The result of this method is used as the value. - * - * @param \DOMElement $parentNode - * @param array $propertyMap - * @return array - */ - static function parseProperties(\DOMElement $parentNode, array $propertyMap = array()) { - - $propList = array(); - foreach($parentNode->childNodes as $propNode) { - - if (self::toClarkNotation($propNode)!=='{DAV:}prop') continue; - - foreach($propNode->childNodes as $propNodeData) { - - /* If there are no elements in here, we actually get 1 text node, this special case is dedicated to netdrive */ - if ($propNodeData->nodeType != XML_ELEMENT_NODE) continue; - - $propertyName = self::toClarkNotation($propNodeData); - if (isset($propertyMap[$propertyName])) { - $propList[$propertyName] = call_user_func(array($propertyMap[$propertyName],'unserialize'),$propNodeData); - } else { - $propList[$propertyName] = $propNodeData->textContent; - } - } - - - } - return $propList; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAVACL/AbstractPrincipalCollection.php b/vendor/sabre/dav/lib/Sabre/DAVACL/AbstractPrincipalCollection.php deleted file mode 100644 index a116236f3..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAVACL/AbstractPrincipalCollection.php +++ /dev/null @@ -1,155 +0,0 @@ -principalPrefix = $principalPrefix; - $this->principalBackend = $principalBackend; - - } - - /** - * This method returns a node for a principal. - * - * The passed array contains principal information, and is guaranteed to - * at least contain a uri item. Other properties may or may not be - * supplied by the authentication backend. - * - * @param array $principalInfo - * @return IPrincipal - */ - abstract function getChildForPrincipal(array $principalInfo); - - /** - * Returns the name of this collection. - * - * @return string - */ - public function getName() { - - list(,$name) = DAV\URLUtil::splitPath($this->principalPrefix); - return $name; - - } - - /** - * Return the list of users - * - * @return array - */ - public function getChildren() { - - if ($this->disableListing) - throw new DAV\Exception\MethodNotAllowed('Listing members of this collection is disabled'); - - $children = array(); - foreach($this->principalBackend->getPrincipalsByPrefix($this->principalPrefix) as $principalInfo) { - - $children[] = $this->getChildForPrincipal($principalInfo); - - - } - return $children; - - } - - /** - * Returns a child object, by its name. - * - * @param string $name - * @throws DAV\Exception\NotFound - * @return IPrincipal - */ - public function getChild($name) { - - $principalInfo = $this->principalBackend->getPrincipalByPath($this->principalPrefix . '/' . $name); - if (!$principalInfo) throw new DAV\Exception\NotFound('Principal with name ' . $name . ' not found'); - return $this->getChildForPrincipal($principalInfo); - - } - - /** - * This method is used to search for principals matching a set of - * properties. - * - * This search is specifically used by RFC3744's principal-property-search - * REPORT. You should at least allow searching on - * http://sabredav.org/ns}email-address. - * - * The actual search should be a unicode-non-case-sensitive search. The - * keys in searchProperties are the WebDAV property names, while the values - * are the property values to search on. - * - * If multiple properties are being searched on, the search should be - * AND'ed. - * - * This method should simply return a list of 'child names', which may be - * used to call $this->getChild in the future. - * - * @param array $searchProperties - * @return array - */ - public function searchPrincipals(array $searchProperties) { - - $result = $this->principalBackend->searchPrincipals($this->principalPrefix, $searchProperties); - $r = array(); - - foreach($result as $row) { - list(, $r[]) = DAV\URLUtil::splitPath($row); - } - - return $r; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAVACL/Exception/AceConflict.php b/vendor/sabre/dav/lib/Sabre/DAVACL/Exception/AceConflict.php deleted file mode 100644 index 6ee9afd73..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAVACL/Exception/AceConflict.php +++ /dev/null @@ -1,35 +0,0 @@ -ownerDocument; - - $np = $doc->createElementNS('DAV:','d:no-ace-conflict'); - $errorNode->appendChild($np); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAVACL/Exception/NeedPrivileges.php b/vendor/sabre/dav/lib/Sabre/DAVACL/Exception/NeedPrivileges.php deleted file mode 100644 index f7e435883..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAVACL/Exception/NeedPrivileges.php +++ /dev/null @@ -1,83 +0,0 @@ -uri = $uri; - $this->privileges = $privileges; - - parent::__construct('User did not have the required privileges (' . implode(',', $privileges) . ') for path "' . $uri . '"'); - - } - - /** - * Adds in extra information in the xml response. - * - * This method adds the {DAV:}need-privileges element as defined in rfc3744 - * - * @param DAV\Server $server - * @param \DOMElement $errorNode - * @return void - */ - public function serialize(DAV\Server $server,\DOMElement $errorNode) { - - $doc = $errorNode->ownerDocument; - - $np = $doc->createElementNS('DAV:','d:need-privileges'); - $errorNode->appendChild($np); - - foreach($this->privileges as $privilege) { - - $resource = $doc->createElementNS('DAV:','d:resource'); - $np->appendChild($resource); - - $resource->appendChild($doc->createElementNS('DAV:','d:href',$server->getBaseUri() . $this->uri)); - - $priv = $doc->createElementNS('DAV:','d:privilege'); - $resource->appendChild($priv); - - preg_match('/^{([^}]*)}(.*)$/',$privilege,$privilegeParts); - $priv->appendChild($doc->createElementNS($privilegeParts[1],'d:' . $privilegeParts[2])); - - - } - - } - -} - diff --git a/vendor/sabre/dav/lib/Sabre/DAVACL/Exception/NoAbstract.php b/vendor/sabre/dav/lib/Sabre/DAVACL/Exception/NoAbstract.php deleted file mode 100644 index ba6f76cdb..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAVACL/Exception/NoAbstract.php +++ /dev/null @@ -1,35 +0,0 @@ -ownerDocument; - - $np = $doc->createElementNS('DAV:','d:no-abstract'); - $errorNode->appendChild($np); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAVACL/Exception/NotRecognizedPrincipal.php b/vendor/sabre/dav/lib/Sabre/DAVACL/Exception/NotRecognizedPrincipal.php deleted file mode 100644 index f61edef07..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAVACL/Exception/NotRecognizedPrincipal.php +++ /dev/null @@ -1,35 +0,0 @@ -ownerDocument; - - $np = $doc->createElementNS('DAV:','d:recognized-principal'); - $errorNode->appendChild($np); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAVACL/Exception/NotSupportedPrivilege.php b/vendor/sabre/dav/lib/Sabre/DAVACL/Exception/NotSupportedPrivilege.php deleted file mode 100644 index 6d30698cd..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAVACL/Exception/NotSupportedPrivilege.php +++ /dev/null @@ -1,35 +0,0 @@ -ownerDocument; - - $np = $doc->createElementNS('DAV:','d:not-supported-privilege'); - $errorNode->appendChild($np); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAVACL/IACL.php b/vendor/sabre/dav/lib/Sabre/DAVACL/IACL.php deleted file mode 100644 index 088ca3eec..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAVACL/IACL.php +++ /dev/null @@ -1,74 +0,0 @@ -getChild in the future. - * - * @param array $searchProperties - * @return array - */ - function searchPrincipals(array $searchProperties); - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAVACL/Plugin.php b/vendor/sabre/dav/lib/Sabre/DAVACL/Plugin.php deleted file mode 100644 index f9bf4bb44..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAVACL/Plugin.php +++ /dev/null @@ -1,1402 +0,0 @@ - 'Display name', - '{http://sabredav.org/ns}email-address' => 'Email address', - ); - - /** - * Any principal uri's added here, will automatically be added to the list - * of ACL's. They will effectively receive {DAV:}all privileges, as a - * protected privilege. - * - * @var array - */ - public $adminPrincipals = array(); - - /** - * Returns a list of features added by this plugin. - * - * This list is used in the response of a HTTP OPTIONS request. - * - * @return array - */ - public function getFeatures() { - - return array('access-control', 'calendarserver-principal-property-search'); - - } - - /** - * Returns a list of available methods for a given url - * - * @param string $uri - * @return array - */ - public function getMethods($uri) { - - return array('ACL'); - - } - - /** - * Returns a plugin name. - * - * Using this name other plugins will be able to access other plugins - * using Sabre\DAV\Server::getPlugin - * - * @return string - */ - public function getPluginName() { - - return 'acl'; - - } - - /** - * Returns a list of reports this plugin supports. - * - * This will be used in the {DAV:}supported-report-set property. - * Note that you still need to subscribe to the 'report' event to actually - * implement them - * - * @param string $uri - * @return array - */ - public function getSupportedReportSet($uri) { - - return array( - '{DAV:}expand-property', - '{DAV:}principal-property-search', - '{DAV:}principal-search-property-set', - ); - - } - - - /** - * Checks if the current user has the specified privilege(s). - * - * You can specify a single privilege, or a list of privileges. - * This method will throw an exception if the privilege is not available - * and return true otherwise. - * - * @param string $uri - * @param array|string $privileges - * @param int $recursion - * @param bool $throwExceptions if set to false, this method won't throw exceptions. - * @throws Sabre\DAVACL\Exception\NeedPrivileges - * @return bool - */ - public function checkPrivileges($uri, $privileges, $recursion = self::R_PARENT, $throwExceptions = true) { - - if (!is_array($privileges)) $privileges = array($privileges); - - $acl = $this->getCurrentUserPrivilegeSet($uri); - - if (is_null($acl)) { - if ($this->allowAccessToNodesWithoutACL) { - return true; - } else { - if ($throwExceptions) - throw new Exception\NeedPrivileges($uri,$privileges); - else - return false; - - } - } - - $failed = array(); - foreach($privileges as $priv) { - - if (!in_array($priv, $acl)) { - $failed[] = $priv; - } - - } - - if ($failed) { - if ($throwExceptions) - throw new Exception\NeedPrivileges($uri,$failed); - else - return false; - } - return true; - - } - - /** - * Returns the standard users' principal. - * - * This is one authorative principal url for the current user. - * This method will return null if the user wasn't logged in. - * - * @return string|null - */ - public function getCurrentUserPrincipal() { - - $authPlugin = $this->server->getPlugin('auth'); - if (is_null($authPlugin)) return null; - /** @var $authPlugin Sabre\DAV\Auth\Plugin */ - - $userName = $authPlugin->getCurrentUser(); - if (!$userName) return null; - - return $this->defaultUsernamePath . '/' . $userName; - - } - - - /** - * Returns a list of principals that's associated to the current - * user, either directly or through group membership. - * - * @return array - */ - public function getCurrentUserPrincipals() { - - $currentUser = $this->getCurrentUserPrincipal(); - - if (is_null($currentUser)) return array(); - - return array_merge( - array($currentUser), - $this->getPrincipalMembership($currentUser) - ); - - } - - /** - * This array holds a cache for all the principals that are associated with - * a single principal. - * - * @var array - */ - protected $principalMembershipCache = array(); - - - /** - * Returns all the principal groups the specified principal is a member of. - * - * @param string $principal - * @return array - */ - public function getPrincipalMembership($mainPrincipal) { - - // First check our cache - if (isset($this->principalMembershipCache[$mainPrincipal])) { - return $this->principalMembershipCache[$mainPrincipal]; - } - - $check = array($mainPrincipal); - $principals = array(); - - while(count($check)) { - - $principal = array_shift($check); - - $node = $this->server->tree->getNodeForPath($principal); - if ($node instanceof IPrincipal) { - foreach($node->getGroupMembership() as $groupMember) { - - if (!in_array($groupMember, $principals)) { - - $check[] = $groupMember; - $principals[] = $groupMember; - - } - - } - - } - - } - - // Store the result in the cache - $this->principalMembershipCache[$mainPrincipal] = $principals; - - return $principals; - - } - - /** - * Returns the supported privilege structure for this ACL plugin. - * - * See RFC3744 for more details. Currently we default on a simple, - * standard structure. - * - * You can either get the list of privileges by a uri (path) or by - * specifying a Node. - * - * @param string|DAV\INode $node - * @return array - */ - public function getSupportedPrivilegeSet($node) { - - if (is_string($node)) { - $node = $this->server->tree->getNodeForPath($node); - } - - if ($node instanceof IACL) { - $result = $node->getSupportedPrivilegeSet(); - - if ($result) - return $result; - } - - return self::getDefaultSupportedPrivilegeSet(); - - } - - /** - * Returns a fairly standard set of privileges, which may be useful for - * other systems to use as a basis. - * - * @return array - */ - static function getDefaultSupportedPrivilegeSet() { - - return array( - 'privilege' => '{DAV:}all', - 'abstract' => true, - 'aggregates' => array( - array( - 'privilege' => '{DAV:}read', - 'aggregates' => array( - array( - 'privilege' => '{DAV:}read-acl', - 'abstract' => true, - ), - array( - 'privilege' => '{DAV:}read-current-user-privilege-set', - 'abstract' => true, - ), - ), - ), // {DAV:}read - array( - 'privilege' => '{DAV:}write', - 'aggregates' => array( - array( - 'privilege' => '{DAV:}write-acl', - 'abstract' => true, - ), - array( - 'privilege' => '{DAV:}write-properties', - 'abstract' => true, - ), - array( - 'privilege' => '{DAV:}write-content', - 'abstract' => true, - ), - array( - 'privilege' => '{DAV:}bind', - 'abstract' => true, - ), - array( - 'privilege' => '{DAV:}unbind', - 'abstract' => true, - ), - array( - 'privilege' => '{DAV:}unlock', - 'abstract' => true, - ), - ), - ), // {DAV:}write - ), - ); // {DAV:}all - - } - - /** - * Returns the supported privilege set as a flat list - * - * This is much easier to parse. - * - * The returned list will be index by privilege name. - * The value is a struct containing the following properties: - * - aggregates - * - abstract - * - concrete - * - * @param string|DAV\INode $node - * @return array - */ - final public function getFlatPrivilegeSet($node) { - - $privs = $this->getSupportedPrivilegeSet($node); - - $flat = array(); - $this->getFPSTraverse($privs, null, $flat); - - return $flat; - - } - - /** - * Traverses the privilege set tree for reordering - * - * This function is solely used by getFlatPrivilegeSet, and would have been - * a closure if it wasn't for the fact I need to support PHP 5.2. - * - * @param array $priv - * @param $concrete - * @param array $flat - * @return void - */ - final private function getFPSTraverse($priv, $concrete, &$flat) { - - $myPriv = array( - 'privilege' => $priv['privilege'], - 'abstract' => isset($priv['abstract']) && $priv['abstract'], - 'aggregates' => array(), - 'concrete' => isset($priv['abstract']) && $priv['abstract']?$concrete:$priv['privilege'], - ); - - if (isset($priv['aggregates'])) - foreach($priv['aggregates'] as $subPriv) $myPriv['aggregates'][] = $subPriv['privilege']; - - $flat[$priv['privilege']] = $myPriv; - - if (isset($priv['aggregates'])) { - - foreach($priv['aggregates'] as $subPriv) { - - $this->getFPSTraverse($subPriv, $myPriv['concrete'], $flat); - - } - - } - - } - - /** - * Returns the full ACL list. - * - * Either a uri or a DAV\INode may be passed. - * - * null will be returned if the node doesn't support ACLs. - * - * @param string|DAV\INode $node - * @return array - */ - public function getACL($node) { - - if (is_string($node)) { - $node = $this->server->tree->getNodeForPath($node); - } - if (!$node instanceof IACL) { - return null; - } - $acl = $node->getACL(); - foreach($this->adminPrincipals as $adminPrincipal) { - $acl[] = array( - 'principal' => $adminPrincipal, - 'privilege' => '{DAV:}all', - 'protected' => true, - ); - } - return $acl; - - } - - /** - * Returns a list of privileges the current user has - * on a particular node. - * - * Either a uri or a DAV\INode may be passed. - * - * null will be returned if the node doesn't support ACLs. - * - * @param string|DAV\INode $node - * @return array - */ - public function getCurrentUserPrivilegeSet($node) { - - if (is_string($node)) { - $node = $this->server->tree->getNodeForPath($node); - } - - $acl = $this->getACL($node); - - if (is_null($acl)) return null; - - $principals = $this->getCurrentUserPrincipals(); - - $collected = array(); - - foreach($acl as $ace) { - - $principal = $ace['principal']; - - switch($principal) { - - case '{DAV:}owner' : - $owner = $node->getOwner(); - if ($owner && in_array($owner, $principals)) { - $collected[] = $ace; - } - break; - - - // 'all' matches for every user - case '{DAV:}all' : - - // 'authenticated' matched for every user that's logged in. - // Since it's not possible to use ACL while not being logged - // in, this is also always true. - case '{DAV:}authenticated' : - $collected[] = $ace; - break; - - // 'unauthenticated' can never occur either, so we simply - // ignore these. - case '{DAV:}unauthenticated' : - break; - - default : - if (in_array($ace['principal'], $principals)) { - $collected[] = $ace; - } - break; - - } - - - } - - // Now we deduct all aggregated privileges. - $flat = $this->getFlatPrivilegeSet($node); - - $collected2 = array(); - while(count($collected)) { - - $current = array_pop($collected); - $collected2[] = $current['privilege']; - - foreach($flat[$current['privilege']]['aggregates'] as $subPriv) { - $collected2[] = $subPriv; - $collected[] = $flat[$subPriv]; - } - - } - - return array_values(array_unique($collected2)); - - } - - /** - * Principal property search - * - * This method can search for principals matching certain values in - * properties. - * - * This method will return a list of properties for the matched properties. - * - * @param array $searchProperties The properties to search on. This is a - * key-value list. The keys are property - * names, and the values the strings to - * match them on. - * @param array $requestedProperties This is the list of properties to - * return for every match. - * @param string $collectionUri The principal collection to search on. - * If this is ommitted, the standard - * principal collection-set will be used. - * @return array This method returns an array structure similar to - * Sabre\DAV\Server::getPropertiesForPath. Returned - * properties are index by a HTTP status code. - * - */ - public function principalSearch(array $searchProperties, array $requestedProperties, $collectionUri = null) { - - if (!is_null($collectionUri)) { - $uris = array($collectionUri); - } else { - $uris = $this->principalCollectionSet; - } - - $lookupResults = array(); - foreach($uris as $uri) { - - $principalCollection = $this->server->tree->getNodeForPath($uri); - if (!$principalCollection instanceof IPrincipalCollection) { - // Not a principal collection, we're simply going to ignore - // this. - continue; - } - - $results = $principalCollection->searchPrincipals($searchProperties); - foreach($results as $result) { - $lookupResults[] = rtrim($uri,'/') . '/' . $result; - } - - } - - $matches = array(); - - foreach($lookupResults as $lookupResult) { - - list($matches[]) = $this->server->getPropertiesForPath($lookupResult, $requestedProperties, 0); - - } - - return $matches; - - } - - /** - * Sets up the plugin - * - * This method is automatically called by the server class. - * - * @param DAV\Server $server - * @return void - */ - public function initialize(DAV\Server $server) { - - $this->server = $server; - $server->subscribeEvent('beforeGetProperties',array($this,'beforeGetProperties')); - - $server->subscribeEvent('beforeMethod', array($this,'beforeMethod'),20); - $server->subscribeEvent('beforeBind', array($this,'beforeBind'),20); - $server->subscribeEvent('beforeUnbind', array($this,'beforeUnbind'),20); - $server->subscribeEvent('updateProperties',array($this,'updateProperties')); - $server->subscribeEvent('beforeUnlock', array($this,'beforeUnlock'),20); - $server->subscribeEvent('report',array($this,'report')); - $server->subscribeEvent('unknownMethod', array($this, 'unknownMethod')); - - array_push($server->protectedProperties, - '{DAV:}alternate-URI-set', - '{DAV:}principal-URL', - '{DAV:}group-membership', - '{DAV:}principal-collection-set', - '{DAV:}current-user-principal', - '{DAV:}supported-privilege-set', - '{DAV:}current-user-privilege-set', - '{DAV:}acl', - '{DAV:}acl-restrictions', - '{DAV:}inherited-acl-set', - '{DAV:}owner', - '{DAV:}group' - ); - - // Automatically mapping nodes implementing IPrincipal to the - // {DAV:}principal resourcetype. - $server->resourceTypeMapping['Sabre\\DAVACL\\IPrincipal'] = '{DAV:}principal'; - - // Mapping the group-member-set property to the HrefList property - // class. - $server->propertyMap['{DAV:}group-member-set'] = 'Sabre\\DAV\\Property\\HrefList'; - - } - - - /* {{{ Event handlers */ - - /** - * Triggered before any method is handled - * - * @param string $method - * @param string $uri - * @return void - */ - public function beforeMethod($method, $uri) { - - $exists = $this->server->tree->nodeExists($uri); - - // If the node doesn't exists, none of these checks apply - if (!$exists) return; - - switch($method) { - - case 'GET' : - case 'HEAD' : - case 'OPTIONS' : - // For these 3 we only need to know if the node is readable. - $this->checkPrivileges($uri,'{DAV:}read'); - break; - - case 'PUT' : - case 'LOCK' : - case 'UNLOCK' : - // This method requires the write-content priv if the node - // already exists, and bind on the parent if the node is being - // created. - // The bind privilege is handled in the beforeBind event. - $this->checkPrivileges($uri,'{DAV:}write-content'); - break; - - - case 'PROPPATCH' : - $this->checkPrivileges($uri,'{DAV:}write-properties'); - break; - - case 'ACL' : - $this->checkPrivileges($uri,'{DAV:}write-acl'); - break; - - case 'COPY' : - case 'MOVE' : - // Copy requires read privileges on the entire source tree. - // If the target exists write-content normally needs to be - // checked, however, we're deleting the node beforehand and - // creating a new one after, so this is handled by the - // beforeUnbind event. - // - // The creation of the new node is handled by the beforeBind - // event. - // - // If MOVE is used beforeUnbind will also be used to check if - // the sourcenode can be deleted. - $this->checkPrivileges($uri,'{DAV:}read',self::R_RECURSIVE); - - break; - - } - - } - - /** - * Triggered before a new node is created. - * - * This allows us to check permissions for any operation that creates a - * new node, such as PUT, MKCOL, MKCALENDAR, LOCK, COPY and MOVE. - * - * @param string $uri - * @return void - */ - public function beforeBind($uri) { - - list($parentUri,$nodeName) = DAV\URLUtil::splitPath($uri); - $this->checkPrivileges($parentUri,'{DAV:}bind'); - - } - - /** - * Triggered before a node is deleted - * - * This allows us to check permissions for any operation that will delete - * an existing node. - * - * @param string $uri - * @return void - */ - public function beforeUnbind($uri) { - - list($parentUri,$nodeName) = DAV\URLUtil::splitPath($uri); - $this->checkPrivileges($parentUri,'{DAV:}unbind',self::R_RECURSIVEPARENTS); - - } - - /** - * Triggered before a node is unlocked. - * - * @param string $uri - * @param DAV\Locks\LockInfo $lock - * @TODO: not yet implemented - * @return void - */ - public function beforeUnlock($uri, DAV\Locks\LockInfo $lock) { - - - } - - /** - * Triggered before properties are looked up in specific nodes. - * - * @param string $uri - * @param DAV\INode $node - * @param array $requestedProperties - * @param array $returnedProperties - * @TODO really should be broken into multiple methods, or even a class. - * @return bool - */ - public function beforeGetProperties($uri, DAV\INode $node, &$requestedProperties, &$returnedProperties) { - - // Checking the read permission - if (!$this->checkPrivileges($uri,'{DAV:}read',self::R_PARENT,false)) { - - // User is not allowed to read properties - if ($this->hideNodesFromListings) { - return false; - } - - // Marking all requested properties as '403'. - foreach($requestedProperties as $key=>$requestedProperty) { - unset($requestedProperties[$key]); - $returnedProperties[403][$requestedProperty] = null; - } - return; - - } - - /* Adding principal properties */ - if ($node instanceof IPrincipal) { - - if (false !== ($index = array_search('{DAV:}alternate-URI-set', $requestedProperties))) { - - unset($requestedProperties[$index]); - $returnedProperties[200]['{DAV:}alternate-URI-set'] = new DAV\Property\HrefList($node->getAlternateUriSet()); - - } - if (false !== ($index = array_search('{DAV:}principal-URL', $requestedProperties))) { - - unset($requestedProperties[$index]); - $returnedProperties[200]['{DAV:}principal-URL'] = new DAV\Property\Href($node->getPrincipalUrl() . '/'); - - } - if (false !== ($index = array_search('{DAV:}group-member-set', $requestedProperties))) { - - unset($requestedProperties[$index]); - $returnedProperties[200]['{DAV:}group-member-set'] = new DAV\Property\HrefList($node->getGroupMemberSet()); - - } - if (false !== ($index = array_search('{DAV:}group-membership', $requestedProperties))) { - - unset($requestedProperties[$index]); - $returnedProperties[200]['{DAV:}group-membership'] = new DAV\Property\HrefList($node->getGroupMembership()); - - } - - if (false !== ($index = array_search('{DAV:}displayname', $requestedProperties))) { - - $returnedProperties[200]['{DAV:}displayname'] = $node->getDisplayName(); - - } - - } - if (false !== ($index = array_search('{DAV:}principal-collection-set', $requestedProperties))) { - - unset($requestedProperties[$index]); - $val = $this->principalCollectionSet; - // Ensuring all collections end with a slash - foreach($val as $k=>$v) $val[$k] = $v . '/'; - $returnedProperties[200]['{DAV:}principal-collection-set'] = new DAV\Property\HrefList($val); - - } - if (false !== ($index = array_search('{DAV:}current-user-principal', $requestedProperties))) { - - unset($requestedProperties[$index]); - if ($url = $this->getCurrentUserPrincipal()) { - $returnedProperties[200]['{DAV:}current-user-principal'] = new Property\Principal(Property\Principal::HREF, $url . '/'); - } else { - $returnedProperties[200]['{DAV:}current-user-principal'] = new Property\Principal(Property\Principal::UNAUTHENTICATED); - } - - } - if (false !== ($index = array_search('{DAV:}supported-privilege-set', $requestedProperties))) { - - unset($requestedProperties[$index]); - $returnedProperties[200]['{DAV:}supported-privilege-set'] = new Property\SupportedPrivilegeSet($this->getSupportedPrivilegeSet($node)); - - } - if (false !== ($index = array_search('{DAV:}current-user-privilege-set', $requestedProperties))) { - - if (!$this->checkPrivileges($uri, '{DAV:}read-current-user-privilege-set', self::R_PARENT, false)) { - $returnedProperties[403]['{DAV:}current-user-privilege-set'] = null; - unset($requestedProperties[$index]); - } else { - $val = $this->getCurrentUserPrivilegeSet($node); - if (!is_null($val)) { - unset($requestedProperties[$index]); - $returnedProperties[200]['{DAV:}current-user-privilege-set'] = new Property\CurrentUserPrivilegeSet($val); - } - } - - } - - /* The ACL property contains all the permissions */ - if (false !== ($index = array_search('{DAV:}acl', $requestedProperties))) { - - if (!$this->checkPrivileges($uri, '{DAV:}read-acl', self::R_PARENT, false)) { - - unset($requestedProperties[$index]); - $returnedProperties[403]['{DAV:}acl'] = null; - - } else { - - $acl = $this->getACL($node); - if (!is_null($acl)) { - unset($requestedProperties[$index]); - $returnedProperties[200]['{DAV:}acl'] = new Property\Acl($this->getACL($node)); - } - - } - - } - - /* The acl-restrictions property contains information on how privileges - * must behave. - */ - if (false !== ($index = array_search('{DAV:}acl-restrictions', $requestedProperties))) { - unset($requestedProperties[$index]); - $returnedProperties[200]['{DAV:}acl-restrictions'] = new Property\AclRestrictions(); - } - - /* Adding ACL properties */ - if ($node instanceof IACL) { - - if (false !== ($index = array_search('{DAV:}owner', $requestedProperties))) { - - unset($requestedProperties[$index]); - $returnedProperties[200]['{DAV:}owner'] = new DAV\Property\Href($node->getOwner() . '/'); - - } - - } - - } - - /** - * This method intercepts PROPPATCH methods and make sure the - * group-member-set is updated correctly. - * - * @param array $propertyDelta - * @param array $result - * @param DAV\INode $node - * @return bool - */ - public function updateProperties(&$propertyDelta, &$result, DAV\INode $node) { - - if (!array_key_exists('{DAV:}group-member-set', $propertyDelta)) - return; - - if (is_null($propertyDelta['{DAV:}group-member-set'])) { - $memberSet = array(); - } elseif ($propertyDelta['{DAV:}group-member-set'] instanceof DAV\Property\HrefList) { - $memberSet = array_map( - array($this->server,'calculateUri'), - $propertyDelta['{DAV:}group-member-set']->getHrefs() - ); - } else { - throw new DAV\Exception('The group-member-set property MUST be an instance of Sabre\DAV\Property\HrefList or null'); - } - - if (!($node instanceof IPrincipal)) { - $result[403]['{DAV:}group-member-set'] = null; - unset($propertyDelta['{DAV:}group-member-set']); - - // Returning false will stop the updateProperties process - return false; - } - - $node->setGroupMemberSet($memberSet); - // We must also clear our cache, just in case - - $this->principalMembershipCache = array(); - - $result[200]['{DAV:}group-member-set'] = null; - unset($propertyDelta['{DAV:}group-member-set']); - - } - - /** - * This method handles HTTP REPORT requests - * - * @param string $reportName - * @param \DOMNode $dom - * @return bool - */ - public function report($reportName, $dom) { - - switch($reportName) { - - case '{DAV:}principal-property-search' : - $this->principalPropertySearchReport($dom); - return false; - case '{DAV:}principal-search-property-set' : - $this->principalSearchPropertySetReport($dom); - return false; - case '{DAV:}expand-property' : - $this->expandPropertyReport($dom); - return false; - - } - - } - - /** - * This event is triggered for any HTTP method that is not known by the - * webserver. - * - * @param string $method - * @param string $uri - * @return bool - */ - public function unknownMethod($method, $uri) { - - if ($method!=='ACL') return; - - $this->httpACL($uri); - return false; - - } - - /** - * This method is responsible for handling the 'ACL' event. - * - * @param string $uri - * @return void - */ - public function httpACL($uri) { - - $body = $this->server->httpRequest->getBody(true); - $dom = DAV\XMLUtil::loadDOMDocument($body); - - $newAcl = - Property\Acl::unserialize($dom->firstChild) - ->getPrivileges(); - - // Normalizing urls - foreach($newAcl as $k=>$newAce) { - $newAcl[$k]['principal'] = $this->server->calculateUri($newAce['principal']); - } - - $node = $this->server->tree->getNodeForPath($uri); - - if (!($node instanceof IACL)) { - throw new DAV\Exception\MethodNotAllowed('This node does not support the ACL method'); - } - - $oldAcl = $this->getACL($node); - - $supportedPrivileges = $this->getFlatPrivilegeSet($node); - - /* Checking if protected principals from the existing principal set are - not overwritten. */ - foreach($oldAcl as $oldAce) { - - if (!isset($oldAce['protected']) || !$oldAce['protected']) continue; - - $found = false; - foreach($newAcl as $newAce) { - if ( - $newAce['privilege'] === $oldAce['privilege'] && - $newAce['principal'] === $oldAce['principal'] && - $newAce['protected'] - ) - $found = true; - } - - if (!$found) - throw new Exception\AceConflict('This resource contained a protected {DAV:}ace, but this privilege did not occur in the ACL request'); - - } - - foreach($newAcl as $newAce) { - - // Do we recognize the privilege - if (!isset($supportedPrivileges[$newAce['privilege']])) { - throw new Exception\NotSupportedPrivilege('The privilege you specified (' . $newAce['privilege'] . ') is not recognized by this server'); - } - - if ($supportedPrivileges[$newAce['privilege']]['abstract']) { - throw new Exception\NoAbstract('The privilege you specified (' . $newAce['privilege'] . ') is an abstract privilege'); - } - - // Looking up the principal - try { - $principal = $this->server->tree->getNodeForPath($newAce['principal']); - } catch (DAV\Exception\NotFound $e) { - throw new Exception\NotRecognizedPrincipal('The specified principal (' . $newAce['principal'] . ') does not exist'); - } - if (!($principal instanceof IPrincipal)) { - throw new Exception\NotRecognizedPrincipal('The specified uri (' . $newAce['principal'] . ') is not a principal'); - } - - } - $node->setACL($newAcl); - - } - - /* }}} */ - - /* Reports {{{ */ - - /** - * The expand-property report is defined in RFC3253 section 3-8. - * - * This report is very similar to a standard PROPFIND. The difference is - * that it has the additional ability to look at properties containing a - * {DAV:}href element, follow that property and grab additional elements - * there. - * - * Other rfc's, such as ACL rely on this report, so it made sense to put - * it in this plugin. - * - * @param \DOMElement $dom - * @return void - */ - protected function expandPropertyReport($dom) { - - $requestedProperties = $this->parseExpandPropertyReportRequest($dom->firstChild->firstChild); - $depth = $this->server->getHTTPDepth(0); - $requestUri = $this->server->getRequestUri(); - - $result = $this->expandProperties($requestUri,$requestedProperties,$depth); - - $dom = new \DOMDocument('1.0','utf-8'); - $dom->formatOutput = true; - $multiStatus = $dom->createElement('d:multistatus'); - $dom->appendChild($multiStatus); - - // Adding in default namespaces - foreach($this->server->xmlNamespaces as $namespace=>$prefix) { - - $multiStatus->setAttribute('xmlns:' . $prefix,$namespace); - - } - - foreach($result as $response) { - $response->serialize($this->server, $multiStatus); - } - - $xml = $dom->saveXML(); - $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); - $this->server->httpResponse->sendStatus(207); - $this->server->httpResponse->sendBody($xml); - - } - - /** - * This method is used by expandPropertyReport to parse - * out the entire HTTP request. - * - * @param \DOMElement $node - * @return array - */ - protected function parseExpandPropertyReportRequest($node) { - - $requestedProperties = array(); - do { - - if (DAV\XMLUtil::toClarkNotation($node)!=='{DAV:}property') continue; - - if ($node->firstChild) { - - $children = $this->parseExpandPropertyReportRequest($node->firstChild); - - } else { - - $children = array(); - - } - - $namespace = $node->getAttribute('namespace'); - if (!$namespace) $namespace = 'DAV:'; - - $propName = '{'.$namespace.'}' . $node->getAttribute('name'); - $requestedProperties[$propName] = $children; - - } while ($node = $node->nextSibling); - - return $requestedProperties; - - } - - /** - * This method expands all the properties and returns - * a list with property values - * - * @param array $path - * @param array $requestedProperties the list of required properties - * @param int $depth - * @return array - */ - protected function expandProperties($path, array $requestedProperties, $depth) { - - $foundProperties = $this->server->getPropertiesForPath($path, array_keys($requestedProperties), $depth); - - $result = array(); - - foreach($foundProperties as $node) { - - foreach($requestedProperties as $propertyName=>$childRequestedProperties) { - - // We're only traversing if sub-properties were requested - if(count($childRequestedProperties)===0) continue; - - // We only have to do the expansion if the property was found - // and it contains an href element. - if (!array_key_exists($propertyName,$node[200])) continue; - - if ($node[200][$propertyName] instanceof DAV\Property\IHref) { - $hrefs = array($node[200][$propertyName]->getHref()); - } elseif ($node[200][$propertyName] instanceof DAV\Property\HrefList) { - $hrefs = $node[200][$propertyName]->getHrefs(); - } - - $childProps = array(); - foreach($hrefs as $href) { - $childProps = array_merge($childProps, $this->expandProperties($href, $childRequestedProperties, 0)); - } - $node[200][$propertyName] = new DAV\Property\ResponseList($childProps); - - } - $result[] = new DAV\Property\Response($node['href'], $node); - - } - - return $result; - - } - - /** - * principalSearchPropertySetReport - * - * This method responsible for handing the - * {DAV:}principal-search-property-set report. This report returns a list - * of properties the client may search on, using the - * {DAV:}principal-property-search report. - * - * @param \DOMDocument $dom - * @return void - */ - protected function principalSearchPropertySetReport(\DOMDocument $dom) { - - $httpDepth = $this->server->getHTTPDepth(0); - if ($httpDepth!==0) { - throw new DAV\Exception\BadRequest('This report is only defined when Depth: 0'); - } - - if ($dom->firstChild->hasChildNodes()) - throw new DAV\Exception\BadRequest('The principal-search-property-set report element is not allowed to have child elements'); - - $dom = new \DOMDocument('1.0','utf-8'); - $dom->formatOutput = true; - $root = $dom->createElement('d:principal-search-property-set'); - $dom->appendChild($root); - // Adding in default namespaces - foreach($this->server->xmlNamespaces as $namespace=>$prefix) { - - $root->setAttribute('xmlns:' . $prefix,$namespace); - - } - - $nsList = $this->server->xmlNamespaces; - - foreach($this->principalSearchPropertySet as $propertyName=>$description) { - - $psp = $dom->createElement('d:principal-search-property'); - $root->appendChild($psp); - - $prop = $dom->createElement('d:prop'); - $psp->appendChild($prop); - - $propName = null; - preg_match('/^{([^}]*)}(.*)$/',$propertyName,$propName); - - $currentProperty = $dom->createElement($nsList[$propName[1]] . ':' . $propName[2]); - $prop->appendChild($currentProperty); - - $descriptionElem = $dom->createElement('d:description'); - $descriptionElem->setAttribute('xml:lang','en'); - $descriptionElem->appendChild($dom->createTextNode($description)); - $psp->appendChild($descriptionElem); - - - } - - $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); - $this->server->httpResponse->sendStatus(200); - $this->server->httpResponse->sendBody($dom->saveXML()); - - } - - /** - * principalPropertySearchReport - * - * This method is responsible for handing the - * {DAV:}principal-property-search report. This report can be used for - * clients to search for groups of principals, based on the value of one - * or more properties. - * - * @param \DOMDocument $dom - * @return void - */ - protected function principalPropertySearchReport(\DOMDocument $dom) { - - list($searchProperties, $requestedProperties, $applyToPrincipalCollectionSet) = $this->parsePrincipalPropertySearchReportRequest($dom); - - $uri = null; - if (!$applyToPrincipalCollectionSet) { - $uri = $this->server->getRequestUri(); - } - $result = $this->principalSearch($searchProperties, $requestedProperties, $uri); - - $prefer = $this->server->getHTTPPRefer(); - - $this->server->httpResponse->sendStatus(207); - $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); - $this->server->httpResponse->setHeader('Vary','Brief,Prefer'); - $this->server->httpResponse->sendBody($this->server->generateMultiStatus($result, $prefer['return-minimal'])); - - } - - /** - * parsePrincipalPropertySearchReportRequest - * - * This method parses the request body from a - * {DAV:}principal-property-search report. - * - * This method returns an array with two elements: - * 1. an array with properties to search on, and their values - * 2. a list of propertyvalues that should be returned for the request. - * - * @param \DOMDocument $dom - * @return array - */ - protected function parsePrincipalPropertySearchReportRequest($dom) { - - $httpDepth = $this->server->getHTTPDepth(0); - if ($httpDepth!==0) { - throw new DAV\Exception\BadRequest('This report is only defined when Depth: 0'); - } - - $searchProperties = array(); - - $applyToPrincipalCollectionSet = false; - - // Parsing the search request - foreach($dom->firstChild->childNodes as $searchNode) { - - if (DAV\XMLUtil::toClarkNotation($searchNode) == '{DAV:}apply-to-principal-collection-set') { - $applyToPrincipalCollectionSet = true; - } - - if (DAV\XMLUtil::toClarkNotation($searchNode)!=='{DAV:}property-search') - continue; - - $propertyName = null; - $propertyValue = null; - - foreach($searchNode->childNodes as $childNode) { - - switch(DAV\XMLUtil::toClarkNotation($childNode)) { - - case '{DAV:}prop' : - $property = DAV\XMLUtil::parseProperties($searchNode); - reset($property); - $propertyName = key($property); - break; - - case '{DAV:}match' : - $propertyValue = $childNode->textContent; - break; - - } - - - } - - if (is_null($propertyName) || is_null($propertyValue)) - throw new DAV\Exception\BadRequest('Invalid search request. propertyname: ' . $propertyName . '. propertvvalue: ' . $propertyValue); - - $searchProperties[$propertyName] = $propertyValue; - - } - - return array($searchProperties, array_keys(DAV\XMLUtil::parseProperties($dom->firstChild)), $applyToPrincipalCollectionSet); - - } - - - /* }}} */ - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAVACL/Principal.php b/vendor/sabre/dav/lib/Sabre/DAVACL/Principal.php deleted file mode 100644 index 89277f850..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAVACL/Principal.php +++ /dev/null @@ -1,281 +0,0 @@ -principalBackend = $principalBackend; - $this->principalProperties = $principalProperties; - - } - - /** - * Returns the full principal url - * - * @return string - */ - public function getPrincipalUrl() { - - return $this->principalProperties['uri']; - - } - - /** - * Returns a list of alternative urls for a principal - * - * This can for example be an email address, or ldap url. - * - * @return array - */ - public function getAlternateUriSet() { - - $uris = array(); - if (isset($this->principalProperties['{DAV:}alternate-URI-set'])) { - - $uris = $this->principalProperties['{DAV:}alternate-URI-set']; - - } - - if (isset($this->principalProperties['{http://sabredav.org/ns}email-address'])) { - $uris[] = 'mailto:' . $this->principalProperties['{http://sabredav.org/ns}email-address']; - } - - return array_unique($uris); - - } - - /** - * Returns the list of group members - * - * If this principal is a group, this function should return - * all member principal uri's for the group. - * - * @return array - */ - public function getGroupMemberSet() { - - return $this->principalBackend->getGroupMemberSet($this->principalProperties['uri']); - - } - - /** - * Returns the list of groups this principal is member of - * - * If this principal is a member of a (list of) groups, this function - * should return a list of principal uri's for it's members. - * - * @return array - */ - public function getGroupMembership() { - - return $this->principalBackend->getGroupMemberShip($this->principalProperties['uri']); - - } - - - /** - * Sets a list of group members - * - * If this principal is a group, this method sets all the group members. - * The list of members is always overwritten, never appended to. - * - * This method should throw an exception if the members could not be set. - * - * @param array $groupMembers - * @return void - */ - public function setGroupMemberSet(array $groupMembers) { - - $this->principalBackend->setGroupMemberSet($this->principalProperties['uri'], $groupMembers); - - } - - - /** - * Returns this principals name. - * - * @return string - */ - public function getName() { - - $uri = $this->principalProperties['uri']; - list(, $name) = DAV\URLUtil::splitPath($uri); - return $name; - - } - - /** - * Returns the name of the user - * - * @return string - */ - public function getDisplayName() { - - if (isset($this->principalProperties['{DAV:}displayname'])) { - return $this->principalProperties['{DAV:}displayname']; - } else { - return $this->getName(); - } - - } - - /** - * Returns a list of properties - * - * @param array $requestedProperties - * @return array - */ - public function getProperties($requestedProperties) { - - $newProperties = array(); - foreach($requestedProperties as $propName) { - - if (isset($this->principalProperties[$propName])) { - $newProperties[$propName] = $this->principalProperties[$propName]; - } - - } - - return $newProperties; - - } - - /** - * Updates this principals properties. - * - * @param array $mutations - * @see Sabre\DAV\IProperties::updateProperties - * @return bool|array - */ - public function updateProperties($mutations) { - - return $this->principalBackend->updatePrincipal($this->principalProperties['uri'], $mutations); - - } - - /** - * Returns the owner principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - public function getOwner() { - - return $this->principalProperties['uri']; - - - } - - /** - * Returns a group principal - * - * This must be a url to a principal, or null if there's no owner - * - * @return string|null - */ - public function getGroup() { - - return null; - - } - - /** - * Returns a list of ACE's for this node. - * - * Each ACE has the following properties: - * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are - * currently the only supported privileges - * * 'principal', a url to the principal who owns the node - * * 'protected' (optional), indicating that this ACE is not allowed to - * be updated. - * - * @return array - */ - public function getACL() { - - return array( - array( - 'privilege' => '{DAV:}read', - 'principal' => $this->getPrincipalUrl(), - 'protected' => true, - ), - ); - - } - - /** - * Updates the ACL - * - * This method will receive a list of new ACE's. - * - * @param array $acl - * @return void - */ - public function setACL(array $acl) { - - throw new DAV\Exception\MethodNotAllowed('Updating ACLs is not allowed here'); - - } - - /** - * Returns the list of supported privileges for this node. - * - * The returned data structure is a list of nested privileges. - * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple - * standard structure. - * - * If null is returned from this method, the default privilege set is used, - * which is fine for most common usecases. - * - * @return array|null - */ - public function getSupportedPrivilegeSet() { - - return null; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAVACL/PrincipalBackend/AbstractBackend.php b/vendor/sabre/dav/lib/Sabre/DAVACL/PrincipalBackend/AbstractBackend.php deleted file mode 100644 index 984f9ad82..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAVACL/PrincipalBackend/AbstractBackend.php +++ /dev/null @@ -1,18 +0,0 @@ - array( - * '{DAV:}prop1' => null, - * ), - * 201 => array( - * '{DAV:}prop2' => null, - * ), - * 403 => array( - * '{DAV:}prop3' => null, - * ), - * 424 => array( - * '{DAV:}prop4' => null, - * ), - * ); - * - * In this previous example prop1 was successfully updated or deleted, and - * prop2 was succesfully created. - * - * prop3 failed to update due to '403 Forbidden' and because of this prop4 - * also could not be updated with '424 Failed dependency'. - * - * This last example was actually incorrect. While 200 and 201 could appear - * in 1 response, if there's any error (403) the other properties should - * always fail with 423 (failed dependency). - * - * But anyway, if you don't want to scratch your head over this, just - * return true or false. - * - * @param string $path - * @param array $mutations - * @return array|bool - */ - function updatePrincipal($path, $mutations); - - /** - * This method is used to search for principals matching a set of - * properties. - * - * This search is specifically used by RFC3744's principal-property-search - * REPORT. You should at least allow searching on - * http://sabredav.org/ns}email-address. - * - * The actual search should be a unicode-non-case-sensitive search. The - * keys in searchProperties are the WebDAV property names, while the values - * are the property values to search on. - * - * If multiple properties are being searched on, the search should be - * AND'ed. - * - * This method should simply return an array with full principal uri's. - * - * If somebody attempted to search on a property the backend does not - * support, you should simply return 0 results. - * - * You can also just return 0 results if you choose to not support - * searching at all, but keep in mind that this may stop certain features - * from working. - * - * @param string $prefixPath - * @param array $searchProperties - * @return array - */ - function searchPrincipals($prefixPath, array $searchProperties); - - /** - * Returns the list of members for a group-principal - * - * @param string $principal - * @return array - */ - function getGroupMemberSet($principal); - - /** - * Returns the list of groups a principal is a member of - * - * @param string $principal - * @return array - */ - function getGroupMembership($principal); - - /** - * Updates the list of group members for a group principal. - * - * The principals should be passed as a list of uri's. - * - * @param string $principal - * @param array $members - * @return void - */ - function setGroupMemberSet($principal, array $members); - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAVACL/PrincipalBackend/PDO.php b/vendor/sabre/dav/lib/Sabre/DAVACL/PrincipalBackend/PDO.php deleted file mode 100644 index 0921768c3..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAVACL/PrincipalBackend/PDO.php +++ /dev/null @@ -1,428 +0,0 @@ - array( - 'dbField' => 'displayname', - ), - - /** - * This property is actually used by the CardDAV plugin, where it gets - * mapped to {http://calendarserver.orgi/ns/}me-card. - * - * The reason we don't straight-up use that property, is because - * me-card is defined as a property on the users' addressbook - * collection. - */ - '{http://sabredav.org/ns}vcard-url' => array( - 'dbField' => 'vcardurl', - ), - /** - * This is the users' primary email-address. - */ - '{http://sabredav.org/ns}email-address' => array( - 'dbField' => 'email', - ), - ); - - /** - * Sets up the backend. - * - * @param PDO $pdo - * @param string $tableName - * @param string $groupMembersTableName - */ - public function __construct(\PDO $pdo, $tableName = 'principals', $groupMembersTableName = 'groupmembers') { - - $this->pdo = $pdo; - $this->tableName = $tableName; - $this->groupMembersTableName = $groupMembersTableName; - - } - - - /** - * Returns a list of principals based on a prefix. - * - * This prefix will often contain something like 'principals'. You are only - * expected to return principals that are in this base path. - * - * You are expected to return at least a 'uri' for every user, you can - * return any additional properties if you wish so. Common properties are: - * {DAV:}displayname - * {http://sabredav.org/ns}email-address - This is a custom SabreDAV - * field that's actualy injected in a number of other properties. If - * you have an email address, use this property. - * - * @param string $prefixPath - * @return array - */ - public function getPrincipalsByPrefix($prefixPath) { - - $fields = array( - 'uri', - ); - - foreach($this->fieldMap as $key=>$value) { - $fields[] = $value['dbField']; - } - $result = $this->pdo->query('SELECT '.implode(',', $fields).' FROM '. $this->tableName); - - $principals = array(); - - while($row = $result->fetch(\PDO::FETCH_ASSOC)) { - - // Checking if the principal is in the prefix - list($rowPrefix) = DAV\URLUtil::splitPath($row['uri']); - if ($rowPrefix !== $prefixPath) continue; - - $principal = array( - 'uri' => $row['uri'], - ); - foreach($this->fieldMap as $key=>$value) { - if ($row[$value['dbField']]) { - $principal[$key] = $row[$value['dbField']]; - } - } - $principals[] = $principal; - - } - - return $principals; - - } - - /** - * Returns a specific principal, specified by it's path. - * The returned structure should be the exact same as from - * getPrincipalsByPrefix. - * - * @param string $path - * @return array - */ - public function getPrincipalByPath($path) { - - $fields = array( - 'id', - 'uri', - ); - - foreach($this->fieldMap as $key=>$value) { - $fields[] = $value['dbField']; - } - $stmt = $this->pdo->prepare('SELECT '.implode(',', $fields).' FROM '. $this->tableName . ' WHERE uri = ?'); - $stmt->execute(array($path)); - - $row = $stmt->fetch(\PDO::FETCH_ASSOC); - if (!$row) return; - - $principal = array( - 'id' => $row['id'], - 'uri' => $row['uri'], - ); - foreach($this->fieldMap as $key=>$value) { - if ($row[$value['dbField']]) { - $principal[$key] = $row[$value['dbField']]; - } - } - return $principal; - - } - - /** - * Updates one ore more webdav properties on a principal. - * - * The list of mutations is supplied as an array. Each key in the array is - * a propertyname, such as {DAV:}displayname. - * - * Each value is the actual value to be updated. If a value is null, it - * must be deleted. - * - * This method should be atomic. It must either completely succeed, or - * completely fail. Success and failure can simply be returned as 'true' or - * 'false'. - * - * It is also possible to return detailed failure information. In that case - * an array such as this should be returned: - * - * array( - * 200 => array( - * '{DAV:}prop1' => null, - * ), - * 201 => array( - * '{DAV:}prop2' => null, - * ), - * 403 => array( - * '{DAV:}prop3' => null, - * ), - * 424 => array( - * '{DAV:}prop4' => null, - * ), - * ); - * - * In this previous example prop1 was successfully updated or deleted, and - * prop2 was succesfully created. - * - * prop3 failed to update due to '403 Forbidden' and because of this prop4 - * also could not be updated with '424 Failed dependency'. - * - * This last example was actually incorrect. While 200 and 201 could appear - * in 1 response, if there's any error (403) the other properties should - * always fail with 423 (failed dependency). - * - * But anyway, if you don't want to scratch your head over this, just - * return true or false. - * - * @param string $path - * @param array $mutations - * @return array|bool - */ - public function updatePrincipal($path, $mutations) { - - $updateAble = array(); - foreach($mutations as $key=>$value) { - - // We are not aware of this field, we must fail. - if (!isset($this->fieldMap[$key])) { - - $response = array( - 403 => array( - $key => null, - ), - 424 => array(), - ); - - // Adding the rest to the response as a 424 - foreach($mutations as $subKey=>$subValue) { - if ($subKey !== $key) { - $response[424][$subKey] = null; - } - } - return $response; - } - - $updateAble[$this->fieldMap[$key]['dbField']] = $value; - - } - - // No fields to update - $query = "UPDATE " . $this->tableName . " SET "; - - $first = true; - foreach($updateAble as $key => $value) { - if (!$first) { - $query.= ', '; - } - $first = false; - $query.= "$key = :$key "; - } - $query.='WHERE uri = :uri'; - $stmt = $this->pdo->prepare($query); - $updateAble['uri'] = $path; - $stmt->execute($updateAble); - - return true; - - } - - /** - * This method is used to search for principals matching a set of - * properties. - * - * This search is specifically used by RFC3744's principal-property-search - * REPORT. You should at least allow searching on - * http://sabredav.org/ns}email-address. - * - * The actual search should be a unicode-non-case-sensitive search. The - * keys in searchProperties are the WebDAV property names, while the values - * are the property values to search on. - * - * If multiple properties are being searched on, the search should be - * AND'ed. - * - * This method should simply return an array with full principal uri's. - * - * If somebody attempted to search on a property the backend does not - * support, you should simply return 0 results. - * - * You can also just return 0 results if you choose to not support - * searching at all, but keep in mind that this may stop certain features - * from working. - * - * @param string $prefixPath - * @param array $searchProperties - * @return array - */ - public function searchPrincipals($prefixPath, array $searchProperties) { - - $query = 'SELECT uri FROM ' . $this->tableName . ' WHERE 1=1 '; - $values = array(); - foreach($searchProperties as $property => $value) { - - switch($property) { - - case '{DAV:}displayname' : - $query.=' AND displayname LIKE ?'; - $values[] = '%' . $value . '%'; - break; - case '{http://sabredav.org/ns}email-address' : - $query.=' AND email LIKE ?'; - $values[] = '%' . $value . '%'; - break; - default : - // Unsupported property - return array(); - - } - - } - $stmt = $this->pdo->prepare($query); - $stmt->execute($values); - - $principals = array(); - while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { - - // Checking if the principal is in the prefix - list($rowPrefix) = DAV\URLUtil::splitPath($row['uri']); - if ($rowPrefix !== $prefixPath) continue; - - $principals[] = $row['uri']; - - } - - return $principals; - - } - - /** - * Returns the list of members for a group-principal - * - * @param string $principal - * @return array - */ - public function getGroupMemberSet($principal) { - - $principal = $this->getPrincipalByPath($principal); - if (!$principal) throw new DAV\Exception('Principal not found'); - - $stmt = $this->pdo->prepare('SELECT principals.uri as uri FROM '.$this->groupMembersTableName.' AS groupmembers LEFT JOIN '.$this->tableName.' AS principals ON groupmembers.member_id = principals.id WHERE groupmembers.principal_id = ?'); - $stmt->execute(array($principal['id'])); - - $result = array(); - while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { - $result[] = $row['uri']; - } - return $result; - - } - - /** - * Returns the list of groups a principal is a member of - * - * @param string $principal - * @return array - */ - public function getGroupMembership($principal) { - - $principal = $this->getPrincipalByPath($principal); - if (!$principal) throw new DAV\Exception('Principal not found'); - - $stmt = $this->pdo->prepare('SELECT principals.uri as uri FROM '.$this->groupMembersTableName.' AS groupmembers LEFT JOIN '.$this->tableName.' AS principals ON groupmembers.principal_id = principals.id WHERE groupmembers.member_id = ?'); - $stmt->execute(array($principal['id'])); - - $result = array(); - while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { - $result[] = $row['uri']; - } - return $result; - - } - - /** - * Updates the list of group members for a group principal. - * - * The principals should be passed as a list of uri's. - * - * @param string $principal - * @param array $members - * @return void - */ - public function setGroupMemberSet($principal, array $members) { - - // Grabbing the list of principal id's. - $stmt = $this->pdo->prepare('SELECT id, uri FROM '.$this->tableName.' WHERE uri IN (? ' . str_repeat(', ? ', count($members)) . ');'); - $stmt->execute(array_merge(array($principal), $members)); - - $memberIds = array(); - $principalId = null; - - while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { - if ($row['uri'] == $principal) { - $principalId = $row['id']; - } else { - $memberIds[] = $row['id']; - } - } - if (!$principalId) throw new DAV\Exception('Principal not found'); - - // Wiping out old members - $stmt = $this->pdo->prepare('DELETE FROM '.$this->groupMembersTableName.' WHERE principal_id = ?;'); - $stmt->execute(array($principalId)); - - foreach($memberIds as $memberId) { - - $stmt = $this->pdo->prepare('INSERT INTO '.$this->groupMembersTableName.' (principal_id, member_id) VALUES (?, ?);'); - $stmt->execute(array($principalId, $memberId)); - - } - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAVACL/PrincipalCollection.php b/vendor/sabre/dav/lib/Sabre/DAVACL/PrincipalCollection.php deleted file mode 100644 index 3aadf399d..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAVACL/PrincipalCollection.php +++ /dev/null @@ -1,33 +0,0 @@ -principalBackend, $principal); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAVACL/Property/Acl.php b/vendor/sabre/dav/lib/Sabre/DAVACL/Property/Acl.php deleted file mode 100644 index e6a70ce91..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAVACL/Property/Acl.php +++ /dev/null @@ -1,211 +0,0 @@ -privileges = $privileges; - $this->prefixBaseUrl = $prefixBaseUrl; - - } - - /** - * Returns the list of privileges for this property - * - * @return array - */ - public function getPrivileges() { - - return $this->privileges; - - } - - /** - * Serializes the property into a DOMElement - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - public function serialize(DAV\Server $server,\DOMElement $node) { - - $doc = $node->ownerDocument; - foreach($this->privileges as $ace) { - - $this->serializeAce($doc, $node, $ace, $server); - - } - - } - - /** - * Unserializes the {DAV:}acl xml element. - * - * @param \DOMElement $dom - * @return Acl - */ - static public function unserialize(\DOMElement $dom) { - - $privileges = array(); - $xaces = $dom->getElementsByTagNameNS('urn:DAV','ace'); - for($ii=0; $ii < $xaces->length; $ii++) { - - $xace = $xaces->item($ii); - $principal = $xace->getElementsByTagNameNS('urn:DAV','principal'); - if ($principal->length !== 1) { - throw new DAV\Exception\BadRequest('Each {DAV:}ace element must have one {DAV:}principal element'); - } - $principal = Principal::unserialize($principal->item(0)); - - switch($principal->getType()) { - case Principal::HREF : - $principal = $principal->getHref(); - break; - case Principal::AUTHENTICATED : - $principal = '{DAV:}authenticated'; - break; - case Principal::UNAUTHENTICATED : - $principal = '{DAV:}unauthenticated'; - break; - case Principal::ALL : - $principal = '{DAV:}all'; - break; - - } - - $protected = false; - - if ($xace->getElementsByTagNameNS('urn:DAV','protected')->length > 0) { - $protected = true; - } - - $grants = $xace->getElementsByTagNameNS('urn:DAV','grant'); - if ($grants->length < 1) { - throw new DAV\Exception\NotImplemented('Every {DAV:}ace element must have a {DAV:}grant element. {DAV:}deny is not yet supported'); - } - $grant = $grants->item(0); - - $xprivs = $grant->getElementsByTagNameNS('urn:DAV','privilege'); - for($jj=0; $jj<$xprivs->length; $jj++) { - - $xpriv = $xprivs->item($jj); - - $privilegeName = null; - - for ($kk=0;$kk<$xpriv->childNodes->length;$kk++) { - - $childNode = $xpriv->childNodes->item($kk); - if ($t = DAV\XMLUtil::toClarkNotation($childNode)) { - $privilegeName = $t; - break; - } - } - if (is_null($privilegeName)) { - throw new DAV\Exception\BadRequest('{DAV:}privilege elements must have a privilege element contained within them.'); - } - - $privileges[] = array( - 'principal' => $principal, - 'protected' => $protected, - 'privilege' => $privilegeName, - ); - - } - - } - - return new self($privileges); - - } - - /** - * Serializes a single access control entry. - * - * @param \DOMDocument $doc - * @param \DOMElement $node - * @param array $ace - * @param DAV\Server $server - * @return void - */ - private function serializeAce($doc,$node,$ace, DAV\Server $server) { - - $xace = $doc->createElementNS('DAV:','d:ace'); - $node->appendChild($xace); - - $principal = $doc->createElementNS('DAV:','d:principal'); - $xace->appendChild($principal); - switch($ace['principal']) { - case '{DAV:}authenticated' : - $principal->appendChild($doc->createElementNS('DAV:','d:authenticated')); - break; - case '{DAV:}unauthenticated' : - $principal->appendChild($doc->createElementNS('DAV:','d:unauthenticated')); - break; - case '{DAV:}all' : - $principal->appendChild($doc->createElementNS('DAV:','d:all')); - break; - default: - $principal->appendChild($doc->createElementNS('DAV:','d:href',($this->prefixBaseUrl?$server->getBaseUri():'') . $ace['principal'] . '/')); - } - - $grant = $doc->createElementNS('DAV:','d:grant'); - $xace->appendChild($grant); - - $privParts = null; - - preg_match('/^{([^}]*)}(.*)$/',$ace['privilege'],$privParts); - - $xprivilege = $doc->createElementNS('DAV:','d:privilege'); - $grant->appendChild($xprivilege); - - $xprivilege->appendChild($doc->createElementNS($privParts[1],'d:'.$privParts[2])); - - if (isset($ace['protected']) && $ace['protected']) - $xace->appendChild($doc->createElement('d:protected')); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAVACL/Property/AclRestrictions.php b/vendor/sabre/dav/lib/Sabre/DAVACL/Property/AclRestrictions.php deleted file mode 100644 index aa6fd17d6..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAVACL/Property/AclRestrictions.php +++ /dev/null @@ -1,34 +0,0 @@ -ownerDocument; - - $elem->appendChild($doc->createElementNS('DAV:','d:grant-only')); - $elem->appendChild($doc->createElementNS('DAV:','d:no-invert')); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAVACL/Property/CurrentUserPrivilegeSet.php b/vendor/sabre/dav/lib/Sabre/DAVACL/Property/CurrentUserPrivilegeSet.php deleted file mode 100644 index e0501dbd6..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAVACL/Property/CurrentUserPrivilegeSet.php +++ /dev/null @@ -1,124 +0,0 @@ -privileges = $privileges; - - } - - /** - * Serializes the property in the DOM - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - public function serialize(DAV\Server $server,\DOMElement $node) { - - $doc = $node->ownerDocument; - foreach($this->privileges as $privName) { - - $this->serializePriv($doc,$node,$privName); - - } - - } - - /** - * Returns true or false, whether the specified principal appears in the - * list. - * - * @return bool - */ - public function has($privilegeName) { - - return in_array($privilegeName, $this->privileges); - - } - - /** - * Serializes one privilege - * - * @param \DOMDocument $doc - * @param \DOMElement $node - * @param string $privName - * @return void - */ - protected function serializePriv($doc,$node,$privName) { - - $xp = $doc->createElementNS('DAV:','d:privilege'); - $node->appendChild($xp); - - $privParts = null; - preg_match('/^{([^}]*)}(.*)$/',$privName,$privParts); - - $xp->appendChild($doc->createElementNS($privParts[1],'d:'.$privParts[2])); - - } - - /** - * Unserializes the {DAV:}current-user-privilege-set element. - * - * @param DOMElement $node - * @return CurrentUserPrivilegeSet - */ - static public function unserialize(\DOMElement $node) { - - $result = array(); - - $xprivs = $node->getElementsByTagNameNS('urn:DAV','privilege'); - - for($jj=0; $jj<$xprivs->length; $jj++) { - - $xpriv = $xprivs->item($jj); - - $privilegeName = null; - - for ($kk=0;$kk<$xpriv->childNodes->length;$kk++) { - - $childNode = $xpriv->childNodes->item($kk); - if ($t = DAV\XMLUtil::toClarkNotation($childNode)) { - $privilegeName = $t; - break; - } - } - - $result[] = $privilegeName; - - } - - return new self($result); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAVACL/Property/Principal.php b/vendor/sabre/dav/lib/Sabre/DAVACL/Property/Principal.php deleted file mode 100644 index 6c644b024..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAVACL/Property/Principal.php +++ /dev/null @@ -1,161 +0,0 @@ -type = $type; - - if ($type===self::HREF && is_null($href)) { - throw new DAV\Exception('The href argument must be specified for the HREF principal type.'); - } - $this->href = $href; - - } - - /** - * Returns the principal type - * - * @return int - */ - public function getType() { - - return $this->type; - - } - - /** - * Returns the principal uri. - * - * @return string - */ - public function getHref() { - - return $this->href; - - } - - /** - * Serializes the property into a DOMElement. - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - public function serialize(DAV\Server $server, \DOMElement $node) { - - $prefix = $server->xmlNamespaces['DAV:']; - switch($this->type) { - - case self::UNAUTHENTICATED : - $node->appendChild( - $node->ownerDocument->createElement($prefix . ':unauthenticated') - ); - break; - case self::AUTHENTICATED : - $node->appendChild( - $node->ownerDocument->createElement($prefix . ':authenticated') - ); - break; - case self::HREF : - $href = $node->ownerDocument->createElement($prefix . ':href'); - $href->nodeValue = $server->getBaseUri() . DAV\URLUtil::encodePath($this->href); - $node->appendChild($href); - break; - - } - - } - - /** - * Deserializes a DOM element into a property object. - * - * @param \DOMElement $dom - * @return Principal - */ - static public function unserialize(\DOMElement $dom) { - - $parent = $dom->firstChild; - while(!DAV\XMLUtil::toClarkNotation($parent)) { - $parent = $parent->nextSibling; - } - - switch(DAV\XMLUtil::toClarkNotation($parent)) { - - case '{DAV:}unauthenticated' : - return new self(self::UNAUTHENTICATED); - case '{DAV:}authenticated' : - return new self(self::AUTHENTICATED); - case '{DAV:}href': - return new self(self::HREF, $parent->textContent); - case '{DAV:}all': - return new self(self::ALL); - default : - throw new DAV\Exception\BadRequest('Unexpected element (' . DAV\XMLUtil::toClarkNotation($parent) . '). Could not deserialize'); - - } - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAVACL/Property/SupportedPrivilegeSet.php b/vendor/sabre/dav/lib/Sabre/DAVACL/Property/SupportedPrivilegeSet.php deleted file mode 100644 index 5f152d9e5..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAVACL/Property/SupportedPrivilegeSet.php +++ /dev/null @@ -1,94 +0,0 @@ -privileges = $privileges; - - } - - /** - * Serializes the property into a domdocument. - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - public function serialize(DAV\Server $server,\DOMElement $node) { - - $doc = $node->ownerDocument; - $this->serializePriv($doc, $node, $this->privileges); - - } - - /** - * Serializes a property - * - * This is a recursive function. - * - * @param \DOMDocument $doc - * @param \DOMElement $node - * @param array $privilege - * @return void - */ - private function serializePriv($doc,$node,$privilege) { - - $xsp = $doc->createElementNS('DAV:','d:supported-privilege'); - $node->appendChild($xsp); - - $xp = $doc->createElementNS('DAV:','d:privilege'); - $xsp->appendChild($xp); - - $privParts = null; - preg_match('/^{([^}]*)}(.*)$/',$privilege['privilege'],$privParts); - - $xp->appendChild($doc->createElementNS($privParts[1],'d:'.$privParts[2])); - - if (isset($privilege['abstract']) && $privilege['abstract']) { - $xsp->appendChild($doc->createElementNS('DAV:','d:abstract')); - } - - if (isset($privilege['description'])) { - $xsp->appendChild($doc->createElementNS('DAV:','d:description',$privilege['description'])); - } - - if (isset($privilege['aggregates'])) { - foreach($privilege['aggregates'] as $subPrivilege) { - $this->serializePriv($doc,$xsp,$subPrivilege); - } - } - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/DAVACL/Version.php b/vendor/sabre/dav/lib/Sabre/DAVACL/Version.php deleted file mode 100644 index 344e22d7b..000000000 --- a/vendor/sabre/dav/lib/Sabre/DAVACL/Version.php +++ /dev/null @@ -1,24 +0,0 @@ -httpRequest->getHeader('Authorization'); - $authHeader = explode(' ',$authHeader); - - if ($authHeader[0]!='AWS' || !isset($authHeader[1])) { - $this->errorCode = self::ERR_NOAWSHEADER; - return false; - } - - list($this->accessKey,$this->signature) = explode(':',$authHeader[1]); - - return true; - - } - - /** - * Returns the username for the request - * - * @return string - */ - public function getAccessKey() { - - return $this->accessKey; - - } - - /** - * Validates the signature based on the secretKey - * - * @param string $secretKey - * @return bool - */ - public function validate($secretKey) { - - $contentMD5 = $this->httpRequest->getHeader('Content-MD5'); - - if ($contentMD5) { - // We need to validate the integrity of the request - $body = $this->httpRequest->getBody(true); - $this->httpRequest->setBody($body,true); - - if ($contentMD5!=base64_encode(md5($body,true))) { - // content-md5 header did not match md5 signature of body - $this->errorCode = self::ERR_MD5CHECKSUMWRONG; - return false; - } - - } - - if (!$requestDate = $this->httpRequest->getHeader('x-amz-date')) - $requestDate = $this->httpRequest->getHeader('Date'); - - if (!$this->validateRFC2616Date($requestDate)) - return false; - - $amzHeaders = $this->getAmzHeaders(); - - $signature = base64_encode( - $this->hmacsha1($secretKey, - $this->httpRequest->getMethod() . "\n" . - $contentMD5 . "\n" . - $this->httpRequest->getHeader('Content-type') . "\n" . - $requestDate . "\n" . - $amzHeaders . - $this->httpRequest->getURI() - ) - ); - - if ($this->signature != $signature) { - - $this->errorCode = self::ERR_INVALIDSIGNATURE; - return false; - - } - - return true; - - } - - - /** - * Returns an HTTP 401 header, forcing login - * - * This should be called when username and password are incorrect, or not supplied at all - * - * @return void - */ - public function requireLogin() { - - $this->httpResponse->setHeader('WWW-Authenticate','AWS'); - $this->httpResponse->sendStatus(401); - - } - - /** - * Makes sure the supplied value is a valid RFC2616 date. - * - * If we would just use strtotime to get a valid timestamp, we have no way of checking if a - * user just supplied the word 'now' for the date header. - * - * This function also makes sure the Date header is within 15 minutes of the operating - * system date, to prevent replay attacks. - * - * @param string $dateHeader - * @return bool - */ - protected function validateRFC2616Date($dateHeader) { - - $date = Util::parseHTTPDate($dateHeader); - - // Unknown format - if (!$date) { - $this->errorCode = self::ERR_INVALIDDATEFORMAT; - return false; - } - - $min = new \DateTime('-15 minutes'); - $max = new \DateTime('+15 minutes'); - - // We allow 15 minutes around the current date/time - if ($date > $max || $date < $min) { - $this->errorCode = self::ERR_REQUESTTIMESKEWED; - return false; - } - - return $date; - - } - - /** - * Returns a list of AMZ headers - * - * @return string - */ - protected function getAmzHeaders() { - - $amzHeaders = array(); - $headers = $this->httpRequest->getHeaders(); - foreach($headers as $headerName => $headerValue) { - if (strpos(strtolower($headerName),'x-amz-')===0) { - $amzHeaders[strtolower($headerName)] = str_replace(array("\r\n"),array(' '),$headerValue) . "\n"; - } - } - ksort($amzHeaders); - - $headerStr = ''; - foreach($amzHeaders as $h=>$v) { - $headerStr.=$h.':'.$v; - } - - return $headerStr; - - } - - /** - * Generates an HMAC-SHA1 signature - * - * @param string $key - * @param string $message - * @return string - */ - private function hmacsha1($key, $message) { - - $blocksize=64; - if (strlen($key)>$blocksize) - $key=pack('H*', sha1($key)); - $key=str_pad($key,$blocksize,chr(0x00)); - $ipad=str_repeat(chr(0x36),$blocksize); - $opad=str_repeat(chr(0x5c),$blocksize); - $hmac = pack('H*',sha1(($key^$opad).pack('H*',sha1(($key^$ipad).$message)))); - return $hmac; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/HTTP/AbstractAuth.php b/vendor/sabre/dav/lib/Sabre/HTTP/AbstractAuth.php deleted file mode 100644 index 1ddf412b7..000000000 --- a/vendor/sabre/dav/lib/Sabre/HTTP/AbstractAuth.php +++ /dev/null @@ -1,111 +0,0 @@ -httpResponse = new Response(); - $this->httpRequest = new Request(); - - } - - /** - * Sets an alternative HTTP response object - * - * @param Response $response - * @return void - */ - public function setHTTPResponse(Response $response) { - - $this->httpResponse = $response; - - } - - /** - * Sets an alternative HTTP request object - * - * @param Request $request - * @return void - */ - public function setHTTPRequest(Request $request) { - - $this->httpRequest = $request; - - } - - - /** - * Sets the realm - * - * The realm is often displayed in authentication dialog boxes - * Commonly an application name displayed here - * - * @param string $realm - * @return void - */ - public function setRealm($realm) { - - $this->realm = $realm; - - } - - /** - * Returns the realm - * - * @return string - */ - public function getRealm() { - - return $this->realm; - - } - - /** - * Returns an HTTP 401 header, forcing login - * - * This should be called when username and password are incorrect, or not supplied at all - * - * @return void - */ - abstract public function requireLogin(); - -} diff --git a/vendor/sabre/dav/lib/Sabre/HTTP/BasicAuth.php b/vendor/sabre/dav/lib/Sabre/HTTP/BasicAuth.php deleted file mode 100644 index 659964faa..000000000 --- a/vendor/sabre/dav/lib/Sabre/HTTP/BasicAuth.php +++ /dev/null @@ -1,67 +0,0 @@ -httpRequest->getRawServerValue('PHP_AUTH_USER')) && ($pass = $this->httpRequest->getRawServerValue('PHP_AUTH_PW'))) { - - return array($user,$pass); - - } - - // Most other webservers - $auth = $this->httpRequest->getHeader('Authorization'); - - // Apache could prefix environment variables with REDIRECT_ when urls - // are passed through mod_rewrite - if (!$auth) { - $auth = $this->httpRequest->getRawServerValue('REDIRECT_HTTP_AUTHORIZATION'); - } - - if (!$auth) return false; - - if (strpos(strtolower($auth),'basic')!==0) return false; - - return explode(':', base64_decode(substr($auth, 6)),2); - - } - - /** - * Returns an HTTP 401 header, forcing login - * - * This should be called when username and password are incorrect, or not supplied at all - * - * @return void - */ - public function requireLogin() { - - $this->httpResponse->setHeader('WWW-Authenticate','Basic realm="' . $this->realm . '"'); - $this->httpResponse->sendStatus(401); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/HTTP/DigestAuth.php b/vendor/sabre/dav/lib/Sabre/HTTP/DigestAuth.php deleted file mode 100644 index aae6d84d6..000000000 --- a/vendor/sabre/dav/lib/Sabre/HTTP/DigestAuth.php +++ /dev/null @@ -1,240 +0,0 @@ -nonce = uniqid(); - $this->opaque = md5($this->realm); - parent::__construct(); - - } - - /** - * Gathers all information from the headers - * - * This method needs to be called prior to anything else. - * - * @return void - */ - public function init() { - - $digest = $this->getDigest(); - $this->digestParts = $this->parseDigest($digest); - - } - - /** - * Sets the quality of protection value. - * - * Possible values are: - * Sabre\HTTP\DigestAuth::QOP_AUTH - * Sabre\HTTP\DigestAuth::QOP_AUTHINT - * - * Multiple values can be specified using logical OR. - * - * QOP_AUTHINT ensures integrity of the request body, but this is not - * supported by most HTTP clients. QOP_AUTHINT also requires the entire - * request body to be md5'ed, which can put strains on CPU and memory. - * - * @param int $qop - * @return void - */ - public function setQOP($qop) { - - $this->qop = $qop; - - } - - /** - * Validates the user. - * - * The A1 parameter should be md5($username . ':' . $realm . ':' . $password); - * - * @param string $A1 - * @return bool - */ - public function validateA1($A1) { - - $this->A1 = $A1; - return $this->validate(); - - } - - /** - * Validates authentication through a password. The actual password must be provided here. - * It is strongly recommended not store the password in plain-text and use validateA1 instead. - * - * @param string $password - * @return bool - */ - public function validatePassword($password) { - - $this->A1 = md5($this->digestParts['username'] . ':' . $this->realm . ':' . $password); - return $this->validate(); - - } - - /** - * Returns the username for the request - * - * @return string - */ - public function getUsername() { - - return $this->digestParts['username']; - - } - - /** - * Validates the digest challenge - * - * @return bool - */ - protected function validate() { - - $A2 = $this->httpRequest->getMethod() . ':' . $this->digestParts['uri']; - - if ($this->digestParts['qop']=='auth-int') { - // Making sure we support this qop value - if (!($this->qop & self::QOP_AUTHINT)) return false; - // We need to add an md5 of the entire request body to the A2 part of the hash - $body = $this->httpRequest->getBody(true); - $this->httpRequest->setBody($body,true); - $A2 .= ':' . md5($body); - } else { - - // We need to make sure we support this qop value - if (!($this->qop & self::QOP_AUTH)) return false; - } - - $A2 = md5($A2); - - $validResponse = md5("{$this->A1}:{$this->digestParts['nonce']}:{$this->digestParts['nc']}:{$this->digestParts['cnonce']}:{$this->digestParts['qop']}:{$A2}"); - - return $this->digestParts['response']==$validResponse; - - - } - - /** - * Returns an HTTP 401 header, forcing login - * - * This should be called when username and password are incorrect, or not supplied at all - * - * @return void - */ - public function requireLogin() { - - $qop = ''; - switch($this->qop) { - case self::QOP_AUTH : $qop = 'auth'; break; - case self::QOP_AUTHINT : $qop = 'auth-int'; break; - case self::QOP_AUTH | self::QOP_AUTHINT : $qop = 'auth,auth-int'; break; - } - - $this->httpResponse->setHeader('WWW-Authenticate','Digest realm="' . $this->realm . '",qop="'.$qop.'",nonce="' . $this->nonce . '",opaque="' . $this->opaque . '"'); - $this->httpResponse->sendStatus(401); - - } - - - /** - * This method returns the full digest string. - * - * It should be compatibile with mod_php format and other webservers. - * - * If the header could not be found, null will be returned - * - * @return mixed - */ - public function getDigest() { - - // mod_php - $digest = $this->httpRequest->getRawServerValue('PHP_AUTH_DIGEST'); - if ($digest) return $digest; - - // most other servers - $digest = $this->httpRequest->getHeader('Authorization'); - - // Apache could prefix environment variables with REDIRECT_ when urls - // are passed through mod_rewrite - if (!$digest) { - $digest = $this->httpRequest->getRawServerValue('REDIRECT_HTTP_AUTHORIZATION'); - } - - if ($digest && strpos(strtolower($digest),'digest')===0) { - return substr($digest,7); - } else { - return null; - } - - } - - - /** - * Parses the different pieces of the digest string into an array. - * - * This method returns false if an incomplete digest was supplied - * - * @param string $digest - * @return mixed - */ - protected function parseDigest($digest) { - - // protect against missing data - $needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1); - $data = array(); - - preg_match_all('@(\w+)=(?:(?:")([^"]+)"|([^\s,$]+))@', $digest, $matches, PREG_SET_ORDER); - - foreach ($matches as $m) { - $data[$m[1]] = $m[2] ? $m[2] : $m[3]; - unset($needed_parts[$m[1]]); - } - - return $needed_parts ? false : $data; - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/HTTP/Request.php b/vendor/sabre/dav/lib/Sabre/HTTP/Request.php deleted file mode 100644 index a71a52b42..000000000 --- a/vendor/sabre/dav/lib/Sabre/HTTP/Request.php +++ /dev/null @@ -1,284 +0,0 @@ -_SERVER = $serverData; - else $this->_SERVER =& $_SERVER; - - if ($postData) $this->_POST = $postData; - else $this->_POST =& $_POST; - - } - - /** - * Returns the value for a specific http header. - * - * This method returns null if the header did not exist. - * - * @param string $name - * @return string - */ - public function getHeader($name) { - - $name = strtoupper(str_replace(array('-'),array('_'),$name)); - if (isset($this->_SERVER['HTTP_' . $name])) { - return $this->_SERVER['HTTP_' . $name]; - } - - // There's a few headers that seem to end up in the top-level - // server array. - switch($name) { - case 'CONTENT_TYPE' : - case 'CONTENT_LENGTH' : - if (isset($this->_SERVER[$name])) { - return $this->_SERVER[$name]; - } - break; - - } - return; - - } - - /** - * Returns all (known) HTTP headers. - * - * All headers are converted to lower-case, and additionally all underscores - * are automatically converted to dashes - * - * @return array - */ - public function getHeaders() { - - $hdrs = array(); - foreach($this->_SERVER as $key=>$value) { - - switch($key) { - case 'CONTENT_LENGTH' : - case 'CONTENT_TYPE' : - $hdrs[strtolower(str_replace('_','-',$key))] = $value; - break; - default : - if (strpos($key,'HTTP_')===0) { - $hdrs[substr(strtolower(str_replace('_','-',$key)),5)] = $value; - } - break; - } - - } - - return $hdrs; - - } - - /** - * Returns the HTTP request method - * - * This is for example POST or GET - * - * @return string - */ - public function getMethod() { - - return $this->_SERVER['REQUEST_METHOD']; - - } - - /** - * Returns the requested uri - * - * @return string - */ - public function getUri() { - - return $this->_SERVER['REQUEST_URI']; - - } - - /** - * Will return protocol + the hostname + the uri - * - * @return string - */ - public function getAbsoluteUri() { - - // Checking if the request was made through HTTPS. The last in line is for IIS - $protocol = isset($this->_SERVER['HTTPS']) && ($this->_SERVER['HTTPS']) && ($this->_SERVER['HTTPS']!='off'); - return ($protocol?'https':'http') . '://' . $this->getHeader('Host') . $this->getUri(); - - } - - /** - * Returns everything after the ? from the current url - * - * @return string - */ - public function getQueryString() { - - return isset($this->_SERVER['QUERY_STRING'])?$this->_SERVER['QUERY_STRING']:''; - - } - - /** - * Returns the HTTP request body body - * - * This method returns a readable stream resource. - * If the asString parameter is set to true, a string is sent instead. - * - * @param bool $asString - * @return resource - */ - public function getBody($asString = false) { - - if (is_null($this->body)) { - if (!is_null(self::$defaultInputStream)) { - $this->body = self::$defaultInputStream; - } else { - $this->body = fopen('php://input','r'); - self::$defaultInputStream = $this->body; - } - } - if ($asString) { - $body = stream_get_contents($this->body); - return $body; - } else { - return $this->body; - } - - } - - /** - * Sets the contents of the HTTP request body - * - * This method can either accept a string, or a readable stream resource. - * - * If the setAsDefaultInputStream is set to true, it means for this run of the - * script the supplied body will be used instead of php://input. - * - * @param mixed $body - * @param bool $setAsDefaultInputStream - * @return void - */ - public function setBody($body,$setAsDefaultInputStream = false) { - - if(is_resource($body)) { - $this->body = $body; - } else { - - $stream = fopen('php://temp','r+'); - fputs($stream,$body); - rewind($stream); - // String is assumed - $this->body = $stream; - } - if ($setAsDefaultInputStream) { - self::$defaultInputStream = $this->body; - } - - } - - /** - * Returns PHP's _POST variable. - * - * The reason this is in a method is so it can be subclassed and - * overridden. - * - * @return array - */ - public function getPostVars() { - - return $this->_POST; - - } - - /** - * Returns a specific item from the _SERVER array. - * - * Do not rely on this feature, it is for internal use only. - * - * @param string $field - * @return string - */ - public function getRawServerValue($field) { - - return isset($this->_SERVER[$field])?$this->_SERVER[$field]:null; - - } - - /** - * Returns the HTTP version specified within the request. - * - * @return string - */ - public function getHTTPVersion() { - - $protocol = $this->getRawServerValue('SERVER_PROTOCOL'); - if ($protocol==='HTTP/1.0') { - return '1.0'; - } else { - return '1.1'; - } - - } - -} - diff --git a/vendor/sabre/dav/lib/Sabre/HTTP/Response.php b/vendor/sabre/dav/lib/Sabre/HTTP/Response.php deleted file mode 100644 index a7fc0da12..000000000 --- a/vendor/sabre/dav/lib/Sabre/HTTP/Response.php +++ /dev/null @@ -1,175 +0,0 @@ - 'Continue', - 101 => 'Switching Protocols', - 102 => 'Processing', - 200 => 'OK', - 201 => 'Created', - 202 => 'Accepted', - 203 => 'Non-Authorative Information', - 204 => 'No Content', - 205 => 'Reset Content', - 206 => 'Partial Content', - 207 => 'Multi-Status', // RFC 4918 - 208 => 'Already Reported', // RFC 5842 - 226 => 'IM Used', // RFC 3229 - 300 => 'Multiple Choices', - 301 => 'Moved Permanently', - 302 => 'Found', - 303 => 'See Other', - 304 => 'Not Modified', - 305 => 'Use Proxy', - 306 => 'Reserved', - 307 => 'Temporary Redirect', - 400 => 'Bad request', - 401 => 'Unauthorized', - 402 => 'Payment Required', - 403 => 'Forbidden', - 404 => 'Not Found', - 405 => 'Method Not Allowed', - 406 => 'Not Acceptable', - 407 => 'Proxy Authentication Required', - 408 => 'Request Timeout', - 409 => 'Conflict', - 410 => 'Gone', - 411 => 'Length Required', - 412 => 'Precondition failed', - 413 => 'Request Entity Too Large', - 414 => 'Request-URI Too Long', - 415 => 'Unsupported Media Type', - 416 => 'Requested Range Not Satisfiable', - 417 => 'Expectation Failed', - 418 => 'I\'m a teapot', // RFC 2324 - 422 => 'Unprocessable Entity', // RFC 4918 - 423 => 'Locked', // RFC 4918 - 424 => 'Failed Dependency', // RFC 4918 - 426 => 'Upgrade required', - 428 => 'Precondition required', // draft-nottingham-http-new-status - 429 => 'Too Many Requests', // draft-nottingham-http-new-status - 431 => 'Request Header Fields Too Large', // draft-nottingham-http-new-status - 500 => 'Internal Server Error', - 501 => 'Not Implemented', - 502 => 'Bad Gateway', - 503 => 'Service Unavailable', - 504 => 'Gateway Timeout', - 505 => 'HTTP Version not supported', - 506 => 'Variant Also Negotiates', - 507 => 'Insufficient Storage', // RFC 4918 - 508 => 'Loop Detected', // RFC 5842 - 509 => 'Bandwidth Limit Exceeded', // non-standard - 510 => 'Not extended', - 511 => 'Network Authentication Required', // draft-nottingham-http-new-status - ); - - return 'HTTP/' . $httpVersion . ' ' . $code . ' ' . $msg[$code]; - - } - - // @codeCoverageIgnoreStart - // We cannot reasonably test header() related methods. - - /** - * Sends an HTTP status header to the client. - * - * @param int $code HTTP status code - * @return bool - */ - public function sendStatus($code) { - - if (!headers_sent()) - return header($this->getStatusMessage($code, $this->defaultHttpVersion)); - else return false; - - } - - /** - * Sets an HTTP header for the response - * - * @param string $name - * @param string $value - * @param bool $replace - * @return bool - */ - public function setHeader($name, $value, $replace = true) { - - $value = str_replace(array("\r","\n"),array('\r','\n'),$value); - if (!headers_sent()) - return header($name . ': ' . $value, $replace); - else return false; - - - } - // @codeCoverageIgnoreEnd - - /** - * Sets a bunch of HTTP Headers - * - * headersnames are specified as keys, value in the array value - * - * @param array $headers - * @return void - */ - public function setHeaders(array $headers) { - - foreach($headers as $key=>$value) - $this->setHeader($key, $value); - - } - - /** - * Sends the entire response body - * - * This method can accept either an open filestream, or a string. - * - * @param mixed $body - * @return void - */ - public function sendBody($body) { - - if (is_resource($body)) { - - file_put_contents('php://output', $body); - - } else { - - // We assume a string - echo $body; - - } - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/HTTP/Util.php b/vendor/sabre/dav/lib/Sabre/HTTP/Util.php deleted file mode 100644 index 147246253..000000000 --- a/vendor/sabre/dav/lib/Sabre/HTTP/Util.php +++ /dev/null @@ -1,82 +0,0 @@ -= 0) - return new \DateTime('@' . $realDate, new \DateTimeZone('UTC')); - - } - - /** - * Transforms a DateTime object to HTTP's most common date format. - * - * We're serializing it as the RFC 1123 date, which, for HTTP must be - * specified as GMT. - * - * @param \DateTime $dateTime - * @return string - */ - static function toHTTPDate(\DateTime $dateTime) { - - // We need to clone it, as we don't want to affect the existing - // DateTime. - $dateTime = clone $dateTime; - $dateTime->setTimeZone(new \DateTimeZone('GMT')); - return $dateTime->format('D, d M Y H:i:s \G\M\T'); - - } - -} diff --git a/vendor/sabre/dav/lib/Sabre/HTTP/Version.php b/vendor/sabre/dav/lib/Sabre/HTTP/Version.php deleted file mode 100644 index 0e913835c..000000000 --- a/vendor/sabre/dav/lib/Sabre/HTTP/Version.php +++ /dev/null @@ -1,24 +0,0 @@ -